]> git.lizzy.rs Git - minetest.git/blobdiff - src/client/tile.cpp
Added "[sheet" to the texture special commands.
[minetest.git] / src / client / tile.cpp
index 7b326c15221d33e71ed0f96f5488b85b8f43c71d..4d2166342893fef5e70dc3ecfdd18dc94b74916c 100644 (file)
@@ -26,14 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/numeric.h"
 #include "irrlichttypes_extrabloated.h"
 #include "debug.h"
-#include "main.h" // for g_settings
 #include "filesys.h"
 #include "settings.h"
 #include "mesh.h"
 #include "log.h"
 #include "gamedef.h"
-#include "strfnd.h"
+#include "util/strfnd.h"
 #include "util/string.h" // for parseColorString()
+#include "imagefilters.h"
+#include "guiscalingfilter.h"
+#include "nodedef.h"
+
 
 #ifdef __ANDROID__
 #include <GLES/gl.h>
@@ -182,42 +185,6 @@ struct TextureInfo
        }
 };
 
-/* Upscale textures to user's requested minimum size.  This is a trick to make
- * filters look as good on low-res textures as on high-res ones, by making
- * low-res textures BECOME high-res ones.  This is helpful for worlds that
- * mix high- and low-res textures, or for mods with least-common-denominator
- * textures that don't have the resources to offer high-res alternatives.
- */
-video::IImage *textureMinSizeUpscale(video::IVideoDriver *driver, video::IImage *orig) {
-       if(orig == NULL)
-               return orig;
-       s32 scaleto = g_settings->getS32("texture_min_size");
-       if (scaleto > 0) {
-
-               /* Calculate scaling needed to make the shortest texture dimension
-                * equal to the target minimum.  If e.g. this is a vertical frames
-                * animation, the short dimension will be the real size.
-                */
-               const core::dimension2d<u32> dim = orig->getDimension();
-               u32 xscale = scaleto / dim.Width;
-               u32 yscale = scaleto / dim.Height;
-               u32 scale = (xscale > yscale) ? xscale : yscale;
-
-               // Never downscale; only scale up by 2x or more.
-               if (scale > 1) {
-                       u32 w = scale * dim.Width;
-                       u32 h = scale * dim.Height;
-                       const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
-                       video::IImage *newimg = driver->createImage(
-                                       orig->getColorFormat(), newdim);
-                       orig->copyToScaling(newimg);
-                       return newimg;
-               }
-       }
-
-       return orig;
-}
-
 /*
        SourceImageCache: A cache used for storing source images.
 */
@@ -227,7 +194,7 @@ class SourceImageCache
 public:
        ~SourceImageCache() {
                for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
-                               iter != m_images.end(); iter++) {
+                               iter != m_images.end(); ++iter) {
                        iter->second->drop();
                }
                m_images.clear();
@@ -259,59 +226,6 @@ class SourceImageCache
                        }
                }
 
-               /* 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);
-                               }
-                       }
-               }
-
                if (need_to_grab)
                        toadd->grab();
                m_images[name] = toadd;
@@ -418,7 +332,15 @@ class TextureSource : public IWritableTextureSource
        */
        video::ITexture* getTexture(u32 id);
 
-       video::ITexture* getTexture(const std::string &name, u32 *id);
+       video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
+
+       /*
+               Get a texture specifically intended for mesh
+               application, i.e. not HUD, compositing, or other 2D
+               use.  This texture may be a different size and may
+               have had additional filters applied.
+       */
+       video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
 
        // Returns a pointer to the irrlicht device
        virtual IrrlichtDevice* getDevice()
@@ -462,6 +384,9 @@ class TextureSource : public IWritableTextureSource
        video::IImage* generateImage(const std::string &name);
 
        video::ITexture* getNormalTexture(const std::string &name);
+       video::SColor getTextureAverageColor(const std::string &name);
+       video::ITexture *getShaderFlagsTexture(bool normamap_present);
+
 private:
 
        // The id of the thread that is allowed to use irrlicht directly
@@ -489,7 +414,7 @@ class TextureSource : public IWritableTextureSource
        // Maps a texture name to an index in the former.
        std::map<std::string, u32> m_name_to_id;
        // The two former containers are behind this mutex
-       JMutex m_textureinfo_cache_mutex;
+       Mutex m_textureinfo_cache_mutex;
 
        // Queued texture fetches (to be processed by the main thread)
        RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
@@ -514,7 +439,7 @@ TextureSource::TextureSource(IrrlichtDevice *device):
 {
        assert(m_device); // Pre-condition
 
-       m_main_thread = get_current_thread_id();
+       m_main_thread = thr_get_current_thread_id();
 
        // Add a NULL TextureInfo as the first index, named ""
        m_textureinfo_cache.push_back(TextureInfo(""));
@@ -536,7 +461,7 @@ TextureSource::~TextureSource()
 
        for (std::vector<TextureInfo>::iterator iter =
                        m_textureinfo_cache.begin();
-                       iter != m_textureinfo_cache.end(); iter++)
+                       iter != m_textureinfo_cache.end(); ++iter)
        {
                //cleanup texture
                if (iter->texture)
@@ -546,7 +471,7 @@ TextureSource::~TextureSource()
 
        for (std::vector<video::ITexture*>::iterator iter =
                        m_texture_trash.begin(); iter != m_texture_trash.end();
-                       iter++) {
+                       ++iter) {
                video::ITexture *t = *iter;
 
                //cleanup trashed texture
@@ -565,7 +490,7 @@ u32 TextureSource::getTextureId(const std::string &name)
                /*
                        See if texture already exists
                */
-               JMutexAutoLock lock(m_textureinfo_cache_mutex);
+               MutexAutoLock lock(m_textureinfo_cache_mutex);
                std::map<std::string, u32>::iterator n;
                n = m_name_to_id.find(name);
                if (n != m_name_to_id.end())
@@ -577,7 +502,7 @@ u32 TextureSource::getTextureId(const std::string &name)
        /*
                Get texture
        */
-       if (get_current_thread_id() == m_main_thread)
+       if (thr_is_current_thread(m_main_thread))
        {
                return generateTexture(name);
        }
@@ -628,10 +553,12 @@ static void blit_with_alpha(video::IImage *src, video::IImage *dst,
 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
                v2s32 src_pos, v2s32 dst_pos, v2u32 size);
 
-// Like blit_with_alpha overlay, but uses an int to calculate the ratio
-// and modifies any destination pixels that are not fully transparent
-static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
-               v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
+// Apply a color to an image.  Uses an int (0-255) to calculate the ratio.
+// If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
+// color alpha with the destination alpha.
+// Otherwise, any pixels that are not fully transparent get the color alpha.
+static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
+               video::SColor color, int ratio, bool keep_alpha);
 
 // Apply a mask to an image
 static void apply_mask(video::IImage *mask, video::IImage *dst,
@@ -668,7 +595,7 @@ u32 TextureSource::generateTexture(const std::string &name)
                /*
                        See if texture already exists
                */
-               JMutexAutoLock lock(m_textureinfo_cache_mutex);
+               MutexAutoLock lock(m_textureinfo_cache_mutex);
                std::map<std::string, u32>::iterator n;
                n = m_name_to_id.find(name);
                if (n != m_name_to_id.end()) {
@@ -679,7 +606,7 @@ u32 TextureSource::generateTexture(const std::string &name)
        /*
                Calling only allowed from main thread
        */
-       if (get_current_thread_id() != m_main_thread) {
+       if (!thr_is_current_thread(m_main_thread)) {
                errorstream<<"TextureSource::generateTexture() "
                                "called not from main thread"<<std::endl;
                return 0;
@@ -688,8 +615,7 @@ u32 TextureSource::generateTexture(const std::string &name)
        video::IVideoDriver *driver = m_device->getVideoDriver();
        sanity_check(driver);
 
-       video::IImage *origimg = generateImage(name);
-       video::IImage *img = textureMinSizeUpscale(driver, origimg);
+       video::IImage *img = generateImage(name);
 
        video::ITexture *tex = NULL;
 
@@ -699,16 +625,15 @@ 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();
-               if((origimg != NULL) && (img != origimg))
-                       origimg->drop();
        }
 
        /*
                Add texture to caches (add NULL textures too)
        */
 
-       JMutexAutoLock lock(m_textureinfo_cache_mutex);
+       MutexAutoLock lock(m_textureinfo_cache_mutex);
 
        u32 id = m_textureinfo_cache.size();
        TextureInfo ti(name, tex);
@@ -720,7 +645,7 @@ u32 TextureSource::generateTexture(const std::string &name)
 
 std::string TextureSource::getTextureName(u32 id)
 {
-       JMutexAutoLock lock(m_textureinfo_cache_mutex);
+       MutexAutoLock lock(m_textureinfo_cache_mutex);
 
        if (id >= m_textureinfo_cache.size())
        {
@@ -735,7 +660,7 @@ std::string TextureSource::getTextureName(u32 id)
 
 video::ITexture* TextureSource::getTexture(u32 id)
 {
-       JMutexAutoLock lock(m_textureinfo_cache_mutex);
+       MutexAutoLock lock(m_textureinfo_cache_mutex);
 
        if (id >= m_textureinfo_cache.size())
                return NULL;
@@ -752,6 +677,11 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
        return getTexture(actual_id);
 }
 
+video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
+{
+       return getTexture(name + "^[applyfiltersformesh", id);
+}
+
 void TextureSource::processQueue()
 {
        /*
@@ -776,7 +706,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im
 {
        //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
 
-       sanity_check(get_current_thread_id() == m_main_thread);
+       sanity_check(thr_is_current_thread(m_main_thread));
 
        m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
        m_source_image_existence.set(name, true);
@@ -784,7 +714,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im
 
 void TextureSource::rebuildImagesAndTextures()
 {
-       JMutexAutoLock lock(m_textureinfo_cache_mutex);
+       MutexAutoLock lock(m_textureinfo_cache_mutex);
 
        video::IVideoDriver* driver = m_device->getVideoDriver();
        sanity_check(driver);
@@ -792,8 +722,7 @@ void TextureSource::rebuildImagesAndTextures()
        // Recreate textures
        for (u32 i=0; i<m_textureinfo_cache.size(); i++){
                TextureInfo *ti = &m_textureinfo_cache[i];
-               video::IImage *origimg = generateImage(ti->name);
-               video::IImage *img = textureMinSizeUpscale(driver, origimg);
+               video::IImage *img = generateImage(ti->name);
 #ifdef __ANDROID__
                img = Align2Npot2(img, driver);
                sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
@@ -803,9 +732,8 @@ 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();
-                       if(origimg && (origimg != img))
-                               origimg->drop();
                }
                video::ITexture *t_old = ti->texture;
                // Replace texture
@@ -925,6 +853,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();
 
@@ -1008,7 +938,7 @@ video::ITexture* TextureSource::generateTextureFromMesh(
        smgr->drop();
 
        // Unset render target
-       driver->setRenderTarget(0, false, true, 0);
+       driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
 
        if (params.delete_texture_on_shutdown)
                m_texture_trash.push_back(rtt);
@@ -1018,11 +948,10 @@ video::ITexture* TextureSource::generateTextureFromMesh(
 
 video::IImage* TextureSource::generateImage(const std::string &name)
 {
-       /*
-               Get the base image
-       */
+       // Get the base image
 
        const char separator = '^';
+       const char escape = '\\';
        const char paren_open = '(';
        const char paren_close = ')';
 
@@ -1030,7 +959,9 @@ video::IImage* TextureSource::generateImage(const std::string &name)
        s32 last_separator_pos = -1;
        u8 paren_bal = 0;
        for (s32 i = name.size() - 1; i >= 0; i--) {
-               switch(name[i]) {
+               if (i > 0 && name[i-1] == escape)
+                       continue;
+               switch (name[i]) {
                case separator:
                        if (paren_bal == 0) {
                                last_separator_pos = i;
@@ -1082,7 +1013,7 @@ video::IImage* TextureSource::generateImage(const std::string &name)
 
        std::string last_part_of_name = name.substr(last_separator_pos + 1);
 
-       /* 
+       /*
                If this name is enclosed in parentheses, generate it
                and blit it onto the base image
        */
@@ -1098,10 +1029,12 @@ video::IImage* TextureSource::generateImage(const std::string &name)
                        return NULL;
                }
                core::dimension2d<u32> dim = tmp->getDimension();
-               if (!baseimg)
-                       baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
-               blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
-               tmp->drop();
+               if (baseimg) {
+                       blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
+                       tmp->drop();
+               } else {
+                       baseimg = tmp;
+               }
        } else if (!generateImagePart(last_part_of_name, baseimg)) {
                // Generate image according to part of name
                errorstream << "generateImage(): "
@@ -1169,9 +1102,27 @@ video::IImage * Align2Npot2(video::IImage * image,
 
 #endif
 
+static std::string unescape_string(const std::string &str, const char esc = '\\')
+{
+       std::string out;
+       size_t pos = 0, cpos;
+       out.reserve(str.size());
+       while (1) {
+               cpos = str.find_first_of(esc, pos);
+               if (cpos == std::string::npos) {
+                       out += str.substr(pos);
+                       break;
+               }
+               out += str.substr(pos, cpos - pos) + str[cpos + 1];
+               pos = cpos + 2;
+       }
+       return out;
+}
+
 bool TextureSource::generateImagePart(std::string part_of_name,
                video::IImage *& baseimg)
 {
+       const char escape = '\\'; // same as in generateImage()
        video::IVideoDriver* driver = m_device->getVideoDriver();
        sanity_check(driver);
 
@@ -1245,7 +1196,28 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                                        core::rect<s32>(pos_from, dim),
                                        video::SColor(255,255,255,255),
                                        NULL);*/
-                       blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
+
+                       core::dimension2d<u32> dim_dst = baseimg->getDimension();
+                       if (dim == dim_dst) {
+                               blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
+                       } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
+                               // Upscale overlying image
+                               video::IImage* scaled_image = m_device->getVideoDriver()->
+                                       createImage(video::ECF_A8R8G8B8, dim_dst);
+                               image->copyToScaling(scaled_image);
+
+                               blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
+                               scaled_image->drop();
+                       } else {
+                               // Upscale base image
+                               video::IImage* scaled_base = m_device->getVideoDriver()->
+                                       createImage(video::ECF_A8R8G8B8, dim);
+                               baseimg->copyToScaling(scaled_base);
+                               baseimg->drop();
+                               baseimg = scaled_base;
+
+                               blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
+                       }
                }
                //cleanup
                image->drop();
@@ -1264,7 +1236,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        Adds a cracking texture
                        N = animation frame count, P = crack progression
                */
-               if (part_of_name.substr(0,6) == "[crack")
+               if (str_starts_with(part_of_name, "[crack"))
                {
                        if (baseimg == NULL) {
                                errorstream<<"generateImagePart(): baseimg == NULL "
@@ -1280,47 +1252,47 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        s32 frame_count = stoi(sf.next(":"));
                        s32 progression = stoi(sf.next(":"));
 
-                       /*
-                               Load crack image.
+                       if (progression >= 0) {
+                               /*
+                                       Load crack image.
 
-                               It is an image with a number of cracking stages
-                               horizontally tiled.
-                       */
-                       video::IImage *img_crack = m_sourcecache.getOrLoad(
+                                       It is an image with a number of cracking stages
+                                       horizontally tiled.
+                               */
+                               video::IImage *img_crack = m_sourcecache.getOrLoad(
                                        "crack_anylength.png", m_device);
 
-                       if (img_crack && progression >= 0)
-                       {
-                               draw_crack(img_crack, baseimg,
+                               if (img_crack) {
+                                       draw_crack(img_crack, baseimg,
                                                use_overlay, frame_count,
                                                progression, driver);
-                               img_crack->drop();
+                                       img_crack->drop();
+                               }
                        }
                }
                /*
                        [combine:WxH:X,Y=filename:X,Y=filename2
-                       Creates a bigger texture from an amount of smaller ones
+                       Creates a bigger texture from any amount of smaller ones
                */
-               else if (part_of_name.substr(0,8) == "[combine")
+               else if (str_starts_with(part_of_name, "[combine"))
                {
                        Strfnd sf(part_of_name);
                        sf.next(":");
                        u32 w0 = stoi(sf.next("x"));
                        u32 h0 = stoi(sf.next(":"));
-                       //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
                        core::dimension2d<u32> dim(w0,h0);
                        if (baseimg == NULL) {
                                baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
                                baseimg->fill(video::SColor(0,0,0,0));
                        }
-                       while (sf.atend() == false) {
+                       while (sf.at_end() == false) {
                                u32 x = stoi(sf.next(","));
                                u32 y = stoi(sf.next("="));
-                               std::string filename = sf.next(":");
+                               std::string filename = unescape_string(sf.next_esc(":", escape), escape);
                                infostream<<"Adding \""<<filename
                                                <<"\" to combined ("<<x<<","<<y<<")"
                                                <<std::endl;
-                               video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
+                               video::IImage *img = generateImage(filename);
                                if (img) {
                                        core::dimension2d<u32> dim = img->getDimension();
                                        infostream<<"Size "<<dim.Width
@@ -1343,9 +1315,9 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
                }
                /*
-                       "[brighten"
+                       [brighten
                */
-               else if (part_of_name.substr(0,9) == "[brighten")
+               else if (str_starts_with(part_of_name, "[brighten"))
                {
                        if (baseimg == NULL) {
                                errorstream<<"generateImagePart(): baseimg==NULL "
@@ -1357,13 +1329,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        brighten(baseimg);
                }
                /*
-                       "[noalpha"
+                       [noalpha
                        Make image completely opaque.
                        Used for the leaves texture when in old leaves mode, so
                        that the transparent parts don't look completely black
                        when simple alpha channel is used for rendering.
                */
-               else if (part_of_name.substr(0,8) == "[noalpha")
+               else if (str_starts_with(part_of_name, "[noalpha"))
                {
                        if (baseimg == NULL){
                                errorstream<<"generateImagePart(): baseimg==NULL "
@@ -1384,10 +1356,10 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
                }
                /*
-                       "[makealpha:R,G,B"
+                       [makealpha:R,G,B
                        Convert one color to transparent.
                */
-               else if (part_of_name.substr(0,11) == "[makealpha:")
+               else if (str_starts_with(part_of_name, "[makealpha:"))
                {
                        if (baseimg == NULL) {
                                errorstream<<"generateImagePart(): baseimg == NULL "
@@ -1400,7 +1372,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        u32 r1 = stoi(sf.next(","));
                        u32 g1 = stoi(sf.next(","));
                        u32 b1 = stoi(sf.next(""));
-                       std::string filename = sf.next("");
 
                        core::dimension2d<u32> dim = baseimg->getDimension();
 
@@ -1424,7 +1395,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
                }
                /*
-                       "[transformN"
+                       [transformN
                        Rotates and/or flips the image.
 
                        N can be a number (between 0 and 7) or a transform name.
@@ -1443,7 +1414,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        The resulting transform will be equivalent to one of the
                        eight existing ones, though (see: dihedral group).
                */
-               else if (part_of_name.substr(0,10) == "[transform")
+               else if (str_starts_with(part_of_name, "[transform"))
                {
                        if (baseimg == NULL) {
                                errorstream<<"generateImagePart(): baseimg == NULL "
@@ -1470,7 +1441,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        Example (a grass block (not actually used in game):
                        "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
                */
-               else if (part_of_name.substr(0,14) == "[inventorycube")
+               else if (str_starts_with(part_of_name, "[inventorycube"))
                {
                        if (baseimg != NULL){
                                errorstream<<"generateImagePart(): baseimg != NULL "
@@ -1587,17 +1558,16 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        [lowpart:percent:filename
                        Adds the lower part of a texture
                */
-               else if (part_of_name.substr(0,9) == "[lowpart:")
+               else if (str_starts_with(part_of_name, "[lowpart:"))
                {
                        Strfnd sf(part_of_name);
                        sf.next(":");
                        u32 percent = stoi(sf.next(":"));
-                       std::string filename = sf.next(":");
-                       //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
+                       std::string filename = unescape_string(sf.next_esc(":", escape), escape);
 
                        if (baseimg == NULL)
                                baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
-                       video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
+                       video::IImage *img = generateImage(filename);
                        if (img)
                        {
                                core::dimension2d<u32> dim = img->getDimension();
@@ -1623,7 +1593,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        Crops a frame of a vertical animation.
                        N = frame count, I = frame index
                */
-               else if (part_of_name.substr(0,15) == "[verticalframe:")
+               else if (str_starts_with(part_of_name, "[verticalframe:"))
                {
                        Strfnd sf(part_of_name);
                        sf.next(":");
@@ -1667,7 +1637,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        [mask:filename
                        Applies a mask to an image
                */
-               else if (part_of_name.substr(0,6) == "[mask:")
+               else if (str_starts_with(part_of_name, "[mask:"))
                {
                        if (baseimg == NULL) {
                                errorstream << "generateImage(): baseimg == NULL "
@@ -1677,12 +1647,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
                        Strfnd sf(part_of_name);
                        sf.next(":");
-                       std::string filename = sf.next(":");
+                       std::string filename = unescape_string(sf.next_esc(":", escape), escape);
 
-                       video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
+                       video::IImage *img = generateImage(filename);
                        if (img) {
                                apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
                                                img->getDimension());
+                               img->drop();
                        } else {
                                errorstream << "generateImage(): Failed to load \""
                                                << filename << "\".";
@@ -1693,7 +1664,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        Overlays image with given color
                        color = color as ColorString
                */
-               else if (part_of_name.substr(0,10) == "[colorize:") {
+               else if (str_starts_with(part_of_name, "[colorize:"))
+               {
                        Strfnd sf(part_of_name);
                        sf.next(":");
                        std::string color_str = sf.next(":");
@@ -1708,16 +1680,179 @@ bool TextureSource::generateImagePart(std::string part_of_name,
 
                        video::SColor color;
                        int ratio = -1;
+                       bool keep_alpha = false;
 
                        if (!parseColorString(color_str, color, false))
                                return false;
 
                        if (is_number(ratio_str))
                                ratio = mystoi(ratio_str, 0, 255);
+                       else if (ratio_str == "alpha")
+                               keep_alpha = true;
+
+                       apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
+               }
+               /*
+                       [applyfiltersformesh
+                       Internal modifier
+               */
+               else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
+               {
+                       // Apply the "clean transparent" filter, if configured.
+                       if (g_settings->getBool("texture_clean_transparent"))
+                               imageCleanTransparent(baseimg, 127);
+
+                       /* Upscale textures to user's requested minimum size.  This is a trick to make
+                        * filters look as good on low-res textures as on high-res ones, by making
+                        * low-res textures BECOME high-res ones.  This is helpful for worlds that
+                        * mix high- and low-res textures, or for mods with least-common-denominator
+                        * textures that don't have the resources to offer high-res alternatives.
+                        */
+                       s32 scaleto = g_settings->getS32("texture_min_size");
+                       if (scaleto > 1) {
+                               const core::dimension2d<u32> dim = baseimg->getDimension();
+
+                               /* Calculate scaling needed to make the shortest texture dimension
+                                * equal to the target minimum.  If e.g. this is a vertical frames
+                                * animation, the short dimension will be the real size.
+                                */
+                               u32 xscale = scaleto / dim.Width;
+                               u32 yscale = scaleto / dim.Height;
+                               u32 scale = (xscale > yscale) ? xscale : yscale;
+
+                               // Never downscale; only scale up by 2x or more.
+                               if (scale > 1) {
+                                       u32 w = scale * dim.Width;
+                                       u32 h = scale * dim.Height;
+                                       const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
+                                       video::IImage *newimg = driver->createImage(
+                                                       baseimg->getColorFormat(), newdim);
+                                       baseimg->copyToScaling(newimg);
+                                       baseimg->drop();
+                                       baseimg = newimg;
+                               }
+                       }
+               }
+               /*
+                       [resize:WxH
+                       Resizes the base image to the given dimensions
+               */
+               else if (str_starts_with(part_of_name, "[resize"))
+               {
+                       if (baseimg == NULL) {
+                               errorstream << "generateImagePart(): baseimg == NULL "
+                                               << "for part_of_name=\""<< part_of_name
+                                               << "\", cancelling." << std::endl;
+                               return false;
+                       }
+
+                       Strfnd sf(part_of_name);
+                       sf.next(":");
+                       u32 width = stoi(sf.next("x"));
+                       u32 height = stoi(sf.next(""));
+                       core::dimension2d<u32> dim(width, height);
+
+                       video::IImage* image = m_device->getVideoDriver()->
+                               createImage(video::ECF_A8R8G8B8, dim);
+                       baseimg->copyToScaling(image);
+                       baseimg->drop();
+                       baseimg = image;
+               }
+               /*
+                       [opacity:R
+                       Makes the base image transparent according to the given ratio.
+                       R must be between 0 and 255.
+                       0 means totally transparent.
+                       255 means totally opaque.
+               */
+               else if (str_starts_with(part_of_name, "[opacity:")) {
+                       if (baseimg == NULL) {
+                               errorstream << "generateImagePart(): baseimg == NULL "
+                                               << "for part_of_name=\"" << part_of_name
+                                               << "\", cancelling." << std::endl;
+                               return false;
+                       }
+
+                       Strfnd sf(part_of_name);
+                       sf.next(":");
+
+                       u32 ratio = mystoi(sf.next(""), 0, 255);
+
+                       core::dimension2d<u32> dim = baseimg->getDimension();
+
+                       for (u32 y = 0; y < dim.Height; y++)
+                       for (u32 x = 0; x < dim.Width; x++)
+                       {
+                               video::SColor c = baseimg->getPixel(x, y);
+                               c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
+                               baseimg->setPixel(x, y, c);
+                       }
+               }
+               /*
+                       [invert:mode
+                       Inverts the given channels of the base image.
+                       Mode may contain the characters "r", "g", "b", "a".
+                       Only the channels that are mentioned in the mode string
+                       will be inverted.
+               */
+               else if (str_starts_with(part_of_name, "[invert:")) {
+                       if (baseimg == NULL) {
+                               errorstream << "generateImagePart(): baseimg == NULL "
+                                               << "for part_of_name=\"" << part_of_name
+                                               << "\", cancelling." << std::endl;
+                               return false;
+                       }
+
+                       Strfnd sf(part_of_name);
+                       sf.next(":");
+
+                       std::string mode = sf.next("");
+                       u32 mask = 0;
+                       if (mode.find("a") != std::string::npos)
+                               mask |= 0xff000000UL;
+                       if (mode.find("r") != std::string::npos)
+                               mask |= 0x00ff0000UL;
+                       if (mode.find("g") != std::string::npos)
+                               mask |= 0x0000ff00UL;
+                       if (mode.find("b") != std::string::npos)
+                               mask |= 0x000000ffUL;
 
                        core::dimension2d<u32> dim = baseimg->getDimension();
-                       video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
 
+                       for (u32 y = 0; y < dim.Height; y++)
+                       for (u32 x = 0; x < dim.Width; x++)
+                       {
+                               video::SColor c = baseimg->getPixel(x, y);
+                               c.color ^= mask;        
+                               baseimg->setPixel(x, y, c);
+                       }
+               }
+               /*
+                       [sheet:WxH:X,Y
+                       Retrieves a tile at position X,Y (in tiles)
+                       from the base image it assumes to be a
+                       tilesheet with dimensions W,H (in tiles).
+               */
+               else if (part_of_name.substr(0,7) == "[sheet:") {
+                       if (baseimg == NULL) {
+                               errorstream << "generateImagePart(): baseimg != NULL "
+                                               << "for part_of_name=\"" << part_of_name
+                                               << "\", cancelling." << std::endl;
+                               return false;
+                       }
+
+                       Strfnd sf(part_of_name);
+                       sf.next(":");
+                       u32 w0 = stoi(sf.next("x"));
+                       u32 h0 = stoi(sf.next(":"));
+                       u32 x0 = stoi(sf.next(","));
+                       u32 y0 = stoi(sf.next(":"));
+
+                       core::dimension2d<u32> img_dim = baseimg->getDimension();
+                       core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
+
+                       video::IImage *img = driver->createImage(
+                                       video::ECF_A8R8G8B8, tile_dim);
                        if (!img) {
                                errorstream << "generateImagePart(): Could not create image "
                                                << "for part_of_name=\"" << part_of_name
@@ -1725,10 +1860,15 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                                return false;
                        }
 
-                       img->fill(video::SColor(color));
-                       // Overlay the colored image
-                       blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
-                       img->drop();
+                       img->fill(video::SColor(0,0,0,0));
+                       v2u32 vdim(tile_dim);
+                       core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
+                       baseimg->copyToWithAlpha(img, v2s32(0), rect,
+                                       video::SColor(255,255,255,255), NULL);
+
+                       // Replace baseimg
+                       baseimg->drop();
+                       baseimg = img;
                }
                else
                {
@@ -1788,6 +1928,9 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
        }
 }
 
+// This function has been disabled because it is currently unused.
+// Feel free to re-enable if you find it handy.
+#if 0
 /*
        Draw an image on top of an another one, using the specified ratio
        modify all partially-opaque pixels in the destination.
@@ -1814,6 +1957,45 @@ static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst
                }
        }
 }
+#endif
+
+/*
+       Apply color to destination
+*/
+static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
+               video::SColor color, int ratio, bool keep_alpha)
+{
+       u32 alpha = color.getAlpha();
+       video::SColor dst_c;
+       if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
+               if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
+                       dst_c = color;
+                       for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
+                       for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
+                               u32 dst_alpha = dst->getPixel(x, y).getAlpha();
+                               if (dst_alpha > 0) {
+                                       dst_c.setAlpha(dst_alpha * alpha / 255);
+                                       dst->setPixel(x, y, dst_c);
+                               }
+                       }
+               } else { // replace the color including the alpha
+                       for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
+                       for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
+                               if (dst->getPixel(x, y).getAlpha() > 0)
+                                       dst->setPixel(x, y, color);
+               }
+       } else {  // interpolate between the color and destination
+               float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
+               for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
+               for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
+                       dst_c = dst->getPixel(x, y);
+                       if (dst_c.getAlpha() > 0) {
+                               dst_c = color.getInterpolated(dst_c, interp);
+                               dst->setPixel(x, y, dst_c);
+                       }
+               }
+       }
+}
 
 /*
        Apply mask to destination
@@ -2029,9 +2211,8 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
 
 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
 {
-       u32 id;
        if (isKnownSourceImage("override_normal.png"))
-               return getTexture("override_normal.png", &id);
+               return getTexture("override_normal.png");
        std::string fname_base = name;
        std::string normal_ext = "_normal.png";
        size_t pos = fname_base.find(".");
@@ -2043,7 +2224,65 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name)
                        fname_base.replace(i, 4, normal_ext);
                        i += normal_ext.length();
                }
-               return getTexture(fname_base, &id);
+               return getTexture(fname_base);
                }
        return NULL;
 }
+
+video::SColor TextureSource::getTextureAverageColor(const std::string &name)
+{
+       video::IVideoDriver *driver = m_device->getVideoDriver();
+       video::SColor c(0, 0, 0, 0);
+       video::ITexture *texture = getTexture(name);
+       video::IImage *image = driver->createImage(texture,
+               core::position2d<s32>(0, 0),
+               texture->getOriginalSize());
+       u32 total = 0;
+       u32 tR = 0;
+       u32 tG = 0;
+       u32 tB = 0;
+       core::dimension2d<u32> dim = image->getDimension();
+       u16 step = 1;
+       if (dim.Width > 16)
+               step = dim.Width / 16;
+       for (u16 x = 0; x < dim.Width; x += step) {
+               for (u16 y = 0; y < dim.Width; y += step) {
+                       c = image->getPixel(x,y);
+                       if (c.getAlpha() > 0) {
+                               total++;
+                               tR += c.getRed();
+                               tG += c.getGreen();
+                               tB += c.getBlue();
+                       }
+               }
+       }
+       image->drop();
+       if (total > 0) {
+               c.setRed(tR / total);
+               c.setGreen(tG / total);
+               c.setBlue(tB / total);
+       }
+       c.setAlpha(255);
+       return c;
+}
+
+
+video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
+{
+       std::string tname = "__shaderFlagsTexture";
+       tname += normalmap_present ? "1" : "0";
+
+       if (isKnownSourceImage(tname)) {
+               return getTexture(tname);
+       } else {
+               video::IVideoDriver *driver = m_device->getVideoDriver();
+               video::IImage *flags_image = driver->createImage(
+                       video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
+               sanity_check(flags_image != NULL);
+               video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
+               flags_image->setPixel(0, 0, c);
+               insertSourceImage(tname, flags_image);
+               flags_image->drop();
+               return getTexture(tname);
+       }
+}