]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/tile.cpp
Install menu textures of minetest_game
[dragonfireclient.git] / src / tile.cpp
index c35952b782e460df83b12724385e648a8b5c707f..5f25e123bdd21556029e407cd81304a7e3cbee91 100644 (file)
@@ -1,18 +1,18 @@
 /*
-Minetest-c55
-Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 
 This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+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 General Public License for more details.
+GNU Lesser General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
+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.
 */
@@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "debug.h"
 #include "main.h" // for g_settings
 #include "filesys.h"
-#include "utility.h"
 #include "settings.h"
 #include "mesh.h"
 #include <ICameraSceneNode.h>
@@ -29,7 +28,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mapnode.h" // For texture atlas making
 #include "nodedef.h" // For texture atlas making
 #include "gamedef.h"
-#include "utility_string.h"
+#include "util/string.h"
+#include "util/container.h"
+#include "util/thread.h"
+#include "util/numeric.h"
 
 /*
        A cache from texture name to texture path
@@ -75,7 +77,7 @@ static bool replace_ext(std::string &path, const char *ext)
 
        If failed, return "".
 */
-static std::string getImagePath(std::string path)
+std::string getImagePath(std::string path)
 {
        // A NULL-ended list of possible image extensions
        const char *extensions[] = {
@@ -131,6 +133,18 @@ std::string getTexturePath(const std::string &filename)
                fullpath = getImagePath(testpath);
        }
        
+       /*
+               Check from $user/textures/all
+       */
+       if(fullpath == "")
+       {
+               std::string texture_path = porting::path_user + DIR_DELIM
+                               + "textures" + DIR_DELIM + "all";
+               std::string testpath = texture_path + DIR_DELIM + filename;
+               // Check all filename extensions. Returns "" if not found.
+               fullpath = getImagePath(testpath);
+       }
+
        /*
                Check from default data directory
        */
@@ -187,48 +201,60 @@ struct SourceAtlasPointer
 class SourceImageCache
 {
 public:
+       ~SourceImageCache() {
+               for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
+                               iter != m_images.end(); iter++) {
+                       iter->second->drop();
+               }
+               m_images.clear();
+       }
        void insert(const std::string &name, video::IImage *img,
                        bool prefer_local, video::IVideoDriver *driver)
        {
                assert(img);
                // Remove old image
-               core::map<std::string, video::IImage*>::Node *n;
+               std::map<std::string, video::IImage*>::iterator n;
                n = m_images.find(name);
-               if(n){
-                       video::IImage *oldimg = n->getValue();
-                       if(oldimg)
-                               oldimg->drop();
+               if(n != m_images.end()){
+                       if(n->second)
+                               n->second->drop();
                }
+
+               video::IImage* toadd = img;
+               bool need_to_grab = true;
+
                // Try to use local texture instead if asked to
                if(prefer_local){
                        std::string path = getTexturePath(name.c_str());
                        if(path != ""){
                                video::IImage *img2 = driver->createImageFromFile(path.c_str());
                                if(img2){
-                                       m_images[name] = img2;
-                                       return;
+                                       toadd = img2;
+                                       need_to_grab = false;
                                }
                        }
                }
-               img->grab();
-               m_images[name] = img;
+
+               if (need_to_grab)
+                       toadd->grab();
+               m_images[name] = toadd;
        }
        video::IImage* get(const std::string &name)
        {
-               core::map<std::string, video::IImage*>::Node *n;
+               std::map<std::string, video::IImage*>::iterator n;
                n = m_images.find(name);
-               if(n)
-                       return n->getValue();
+               if(n != m_images.end())
+                       return n->second;
                return NULL;
        }
        // Primarily fetches from cache, secondarily tries to read from filesystem
        video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
        {
-               core::map<std::string, video::IImage*>::Node *n;
+               std::map<std::string, video::IImage*>::iterator n;
                n = m_images.find(name);
-               if(n){
-                       n->getValue()->grab(); // Grab for caller
-                       return n->getValue();
+               if(n != m_images.end()){
+                       n->second->grab(); // Grab for caller
+                       return n->second;
                }
                video::IVideoDriver* driver = device->getVideoDriver();
                std::string path = getTexturePath(name.c_str());
@@ -240,8 +266,7 @@ class SourceImageCache
                infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
                                <<"\""<<std::endl;
                video::IImage *img = driver->createImageFromFile(path.c_str());
-               // Even if could not be loaded, put as NULL
-               //m_images[name] = img;
+
                if(img){
                        m_images[name] = img;
                        img->grab(); // Grab for caller
@@ -249,7 +274,7 @@ class SourceImageCache
                return img;
        }
 private:
-       core::map<std::string, video::IImage*> m_images;
+       std::map<std::string, video::IImage*> m_images;
 };
 
 /*
@@ -260,7 +285,7 @@ class TextureSource : public IWritableTextureSource
 {
 public:
        TextureSource(IrrlichtDevice *device);
-       ~TextureSource();
+       virtual ~TextureSource();
 
        /*
                Example case:
@@ -358,6 +383,18 @@ class TextureSource : public IWritableTextureSource
        // Update new texture pointer and texture coordinates to an
        // AtlasPointer based on it's texture id
        void updateAP(AtlasPointer &ap);
+       bool isKnownSourceImage(const std::string &name)
+       {
+               bool is_known = false;
+               bool cache_found = m_source_image_existence.get(name, &is_known);
+               if(cache_found)
+                       return is_known;
+               // Not found in cache; find out if a local file exists
+               is_known = (getTexturePath(name) != "");
+               m_source_image_existence.set(name, is_known);
+               return is_known;
+       }
 
        // Processes queued texture requests from other threads.
        // Shall be called from the main thread.
@@ -386,11 +423,14 @@ class TextureSource : public IWritableTextureSource
        // This should be only accessed from the main thread
        SourceImageCache m_sourcecache;
 
+       // Thread-safe cache of what source images are known (true = known)
+       MutexedMap<std::string, bool> m_source_image_existence;
+
        // A texture id is index in this array.
        // The first position contains a NULL texture.
-       core::array<SourceAtlasPointer> m_atlaspointer_cache;
+       std::vector<SourceAtlasPointer> m_atlaspointer_cache;
        // Maps a texture name to an index in the former.
-       core::map<std::string, u32> m_name_to_id;
+       std::map<std::string, u32> m_name_to_id;
        // The two former containers are behind this mutex
        JMutex m_atlaspointer_cache_mutex;
        
@@ -425,6 +465,28 @@ TextureSource::TextureSource(IrrlichtDevice *device):
 
 TextureSource::~TextureSource()
 {
+       video::IVideoDriver* driver = m_device->getVideoDriver();
+
+       unsigned int textures_before = driver->getTextureCount();
+
+       for (std::vector<SourceAtlasPointer>::iterator iter =
+                       m_atlaspointer_cache.begin();  iter != m_atlaspointer_cache.end();
+                       iter++)
+       {
+               video::ITexture *t = driver->getTexture(iter->name.c_str());
+
+               //cleanup texture
+               if (t)
+                       driver->removeTexture(t);
+
+               //cleanup source image
+               if (iter->atlas_img)
+                       iter->atlas_img->drop();
+       }
+       m_atlaspointer_cache.clear();
+
+       infostream << "~TextureSource() "<< textures_before << "/"
+                       << driver->getTextureCount() << std::endl;
 }
 
 u32 TextureSource::getTextureId(const std::string &name)
@@ -436,11 +498,11 @@ u32 TextureSource::getTextureId(const std::string &name)
                        See if texture already exists
                */
                JMutexAutoLock lock(m_atlaspointer_cache_mutex);
-               core::map<std::string, u32>::Node *n;
+               std::map<std::string, u32>::iterator n;
                n = m_name_to_id.find(name);
-               if(n != NULL)
+               if(n != m_name_to_id.end())
                {
-                       return n->getValue();
+                       return n->second;
                }
        }
        
@@ -490,8 +552,19 @@ u32 TextureSource::getTextureId(const std::string &name)
 // Overlay image on top of another image (used for cracks)
 void overlay(video::IImage *image, video::IImage *overlay);
 
+// Draw an image on top of an another one, using the alpha channel of the
+// source image
+static void blit_with_alpha(video::IImage *src, video::IImage *dst,
+               v2s32 src_pos, v2s32 dst_pos, v2u32 size);
+
 // Brighten image
 void brighten(video::IImage *image);
+// Parse a transform name
+u32 parseImageTransform(const std::string& s);
+// Apply transform to image dimension
+core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
+// Apply transform to image data
+void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
 
 /*
        Generate image based on a string like "stone.png" or "[crack0".
@@ -539,13 +612,13 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
        {
                JMutexAutoLock lock(m_atlaspointer_cache_mutex);
 
-               core::map<std::string, u32>::Node *n;
+               std::map<std::string, u32>::iterator n;
                n = m_name_to_id.find(name);
-               if(n != NULL)
+               if(n != m_name_to_id.end())
                {
                        /*infostream<<"getTextureIdDirect(): \""<<name
                                        <<"\" found in cache"<<std::endl;*/
-                       return n->getValue();
+                       return n->second;
                }
        }
 
@@ -684,7 +757,7 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
                baseimg_dim = baseimg->getDimension();
        SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
        m_atlaspointer_cache.push_back(nap);
-       m_name_to_id.insert(name, id);
+       m_name_to_id[name] = id;
 
        /*infostream<<"getTextureIdDirect(): "
                        <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
@@ -729,7 +802,7 @@ void TextureSource::processQueue()
        /*
                Fetch textures
        */
-       if(m_get_texture_queue.size() > 0)
+       if(!m_get_texture_queue.empty())
        {
                GetRequest<std::string, u32, u8, u8>
                                request = m_get_texture_queue.pop();
@@ -756,6 +829,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im
        assert(get_current_thread_id() == m_main_thread);
        
        m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
+       m_source_image_existence.set(name, true);
 }
        
 void TextureSource::rebuildImagesAndTextures()
@@ -785,7 +859,7 @@ void TextureSource::rebuildImagesAndTextures()
                video::ITexture *t = NULL;
                if(img)
                        t = driver->addTexture(sap->name.c_str(), img);
-               
+               video::ITexture *t_old = sap->a.atlas;
                // Replace texture
                sap->a.atlas = t;
                sap->a.pos = v2f(0,0);
@@ -794,6 +868,9 @@ void TextureSource::rebuildImagesAndTextures()
                sap->atlas_img = img;
                sap->intpos = v2s32(0,0);
                sap->intsize = img->getDimension();
+
+               if (t_old != 0)
+                       driver->removeTexture(t_old);
        }
 }
 
@@ -812,7 +889,10 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
        JMutexAutoLock lock(m_atlaspointer_cache_mutex);
 
        // Create an image of the right size
-       core::dimension2d<u32> atlas_dim(1024,1024);
+       core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
+       core::dimension2d<u32> atlas_dim(2048,2048);
+       atlas_dim.Width  = MYMIN(atlas_dim.Width,  max_dim.Width);
+       atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
        video::IImage *atlas_img =
                        driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
        //assert(atlas_img);
@@ -828,7 +908,7 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
                main content features
        */
 
-       core::map<std::string, bool> sourcelist;
+       std::set<std::string> sourcelist;
 
        for(u16 j=0; j<MAX_CONTENT+1; j++)
        {
@@ -837,39 +917,40 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
                const ContentFeatures &f = ndef->get(j);
                for(u32 i=0; i<6; i++)
                {
-                       std::string name = f.tname_tiles[i];
-                       sourcelist[name] = true;
+                       std::string name = f.tiledef[i].name;
+                       sourcelist.insert(name);
                }
        }
        
        infostream<<"Creating texture atlas out of textures: ";
-       for(core::map<std::string, bool>::Iterator
-                       i = sourcelist.getIterator();
-                       i.atEnd() == false; i++)
+       for(std::set<std::string>::iterator
+                       i = sourcelist.begin();
+                       i != sourcelist.end(); ++i)
        {
-               std::string name = i.getNode()->getKey();
+               std::string name = *i;
                infostream<<"\""<<name<<"\" ";
        }
        infostream<<std::endl;
 
        // Padding to disallow texture bleeding
+       // (16 needed if mipmapping is used; otherwise less will work too)
        s32 padding = 16;
-
-       s32 column_width = 256;
        s32 column_padding = 16;
+       s32 column_width = 256; // Space for 16 pieces of 16x16 textures
 
        /*
                First pass: generate almost everything
        */
        core::position2d<s32> pos_in_atlas(0,0);
        
+       pos_in_atlas.X = column_padding;
        pos_in_atlas.Y = padding;
 
-       for(core::map<std::string, bool>::Iterator
-                       i = sourcelist.getIterator();
-                       i.atEnd() == false; i++)
+       for(std::set<std::string>::iterator
+                       i = sourcelist.begin();
+                       i != sourcelist.end(); ++i)
        {
-               std::string name = i.getNode()->getKey();
+               std::string name = *i;
 
                // Generate image by name
                video::IImage *img2 = generate_image_from_scratch(name, m_device,
@@ -883,8 +964,8 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
 
                core::dimension2d<u32> dim = img2->getDimension();
 
-               // Don't add to atlas if image is large
-               core::dimension2d<u32> max_size_in_atlas(32,32);
+               // Don't add to atlas if image is too large
+               core::dimension2d<u32> max_size_in_atlas(64,64);
                if(dim.Width > max_size_in_atlas.Width
                || dim.Height > max_size_in_atlas.Height)
                {
@@ -896,14 +977,14 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
                // Wrap columns and stop making atlas if atlas is full
                if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
                {
-                       if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
+                       if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
                                errorstream<<"TextureSource::buildMainAtlas(): "
                                                <<"Atlas is full, not adding more textures."
                                                <<std::endl;
                                break;
                        }
                        pos_in_atlas.Y = padding;
-                       pos_in_atlas.X += column_width + column_padding;
+                       pos_in_atlas.X += column_width + column_padding*2;
                }
                
                /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
@@ -949,6 +1030,29 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
                        atlas_img->setPixel(x,dst_y,c);
                }
 
+               for(u32 side=0; side<2; side++) // left and right
+               for(s32 x0=0; x0<column_padding; x0++)
+               for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
+               {
+                       s32 dst_x;
+                       s32 src_x;
+                       if(side==0)
+                       {
+                               dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
+                               src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
+                       }
+                       else
+                       {
+                               dst_x = -x0 + pos_in_atlas.X-1;
+                               src_x = pos_in_atlas.X;
+                       }
+                       s32 y = y0 + pos_in_atlas.Y;
+                       s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
+                       s32 dst_y = y;
+                       video::SColor c = atlas_img->getPixel(src_x, src_y);
+                       atlas_img->setPixel(dst_x,dst_y,c);
+               }
+
                img2->drop();
 
                /*
@@ -958,11 +1062,11 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
                bool reuse_old_id = false;
                u32 id = m_atlaspointer_cache.size();
                // Check old id without fetching a texture
-               core::map<std::string, u32>::Node *n;
+               std::map<std::string, u32>::iterator n;
                n = m_name_to_id.find(name);
                // If it exists, we will replace the old definition
-               if(n){
-                       id = n->getValue();
+               if(n != m_name_to_id.end()){
+                       id = n->second;
                        reuse_old_id = true;
                        /*infostream<<"TextureSource::buildMainAtlas(): "
                                        <<"Replacing old AtlasPointer"<<std::endl;*/
@@ -998,12 +1102,12 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef)
        /*
                Second pass: set texture pointer in generated AtlasPointers
        */
-       for(core::map<std::string, bool>::Iterator
-                       i = sourcelist.getIterator();
-                       i.atEnd() == false; i++)
+       for(std::set<std::string>::iterator
+                       i = sourcelist.begin();
+                       i != sourcelist.end(); ++i)
        {
-               std::string name = i.getNode()->getKey();
-               if(m_name_to_id.find(name) == NULL)
+               std::string name = *i;
+               if(m_name_to_id.find(name) == m_name_to_id.end())
                        continue;
                u32 id = m_name_to_id[name];
                //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
@@ -1134,7 +1238,6 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                        core::dimension2d<u32> dim = image->getDimension();
                        baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
                        image->copyTo(baseimg);
-                       image->drop();
                }
                // Else blit on base.
                else
@@ -1148,13 +1251,14 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                        // Position to copy the blitted from in the blitted image
                        core::position2d<s32> pos_from(0,0);
                        // Blit
-                       image->copyToWithAlpha(baseimg, pos_to,
+                       /*image->copyToWithAlpha(baseimg, pos_to,
                                        core::rect<s32>(pos_from, dim),
                                        video::SColor(255,255,255,255),
-                                       NULL);
-                       // Drop image
-                       image->drop();
+                                       NULL);*/
+                       blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
                }
+               //cleanup
+               image->drop();
        }
        else
        {
@@ -1221,7 +1325,8 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                                It is an image with a number of cracking stages
                                horizontally tiled.
                        */
-                       video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
+                       video::IImage *img_crack = sourcecache->getOrLoad(
+                                       "crack_anylength.png", device);
                
                        if(img_crack && progression >= 0)
                        {
@@ -1260,11 +1365,13 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                                        }
                                        else
                                        {
-                                               img_crack_scaled->copyToWithAlpha(
+                                               /*img_crack_scaled->copyToWithAlpha(
                                                                baseimg,
                                                                v2s32(0,0),
                                                                core::rect<s32>(v2s32(0,0), dim_base),
-                                                               video::SColor(255,255,255,255));
+                                                               video::SColor(255,255,255,255));*/
+                                               blit_with_alpha(img_crack_scaled, baseimg,
+                                                               v2s32(0,0), v2s32(0,0), dim_base);
                                        }
                                }
 
@@ -1289,7 +1396,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                        u32 h0 = stoi(sf.next(":"));
                        infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
                        core::dimension2d<u32> dim(w0,h0);
-                       baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
+                       if(baseimg == NULL)
+                       {
+                               baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
+                               baseimg->fill(video::SColor(0,0,0,0));
+                       }
                        while(sf.atend() == false)
                        {
                                u32 x = stoi(sf.next(","));
@@ -1309,10 +1420,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                                                        driver->createImage(video::ECF_A8R8G8B8, dim);
                                        img->copyTo(img2);
                                        img->drop();
-                                       img2->copyToWithAlpha(baseimg, pos_base,
+                                       /*img2->copyToWithAlpha(baseimg, pos_base,
                                                        core::rect<s32>(v2s32(0,0), dim),
                                                        video::SColor(255,255,255,255),
-                                                       NULL);
+                                                       NULL);*/
+                                       blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
                                        img2->drop();
                                }
                                else
@@ -1405,6 +1517,46 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                                baseimg->setPixel(x,y,c);
                        }
                }
+               /*
+                       "[transformN"
+                       Rotates and/or flips the image.
+
+                       N can be a number (between 0 and 7) or a transform name.
+                       Rotations are counter-clockwise.
+                       0  I      identity
+                       1  R90    rotate by 90 degrees
+                       2  R180   rotate by 180 degrees
+                       3  R270   rotate by 270 degrees
+                       4  FX     flip X
+                       5  FXR90  flip X then rotate by 90 degrees
+                       6  FY     flip Y
+                       7  FYR90  flip Y then rotate by 90 degrees
+
+                       Note: Transform names can be concatenated to produce
+                       their product (applies the first then the second).
+                       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")
+               {
+                       if(baseimg == NULL)
+                       {
+                               errorstream<<"generate_image(): baseimg==NULL "
+                                               <<"for part_of_name=\""<<part_of_name
+                                               <<"\", cancelling."<<std::endl;
+                               return false;
+                       }
+
+                       u32 transform = parseImageTransform(part_of_name.substr(10));
+                       core::dimension2d<u32> dim = imageTransformDimension(
+                                       transform, baseimg->getDimension());
+                       video::IImage *image = driver->createImage(
+                                       baseimg->getColorFormat(), dim);
+                       assert(image);
+                       imageTransform(transform, baseimg, image);
+                       baseimg->drop();
+                       baseimg = image;
+               }
                /*
                        [inventorycube{topimage{leftimage{rightimage
                        In every subimage, replace ^ with &.
@@ -1510,6 +1662,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                        video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
                        assert(image);
 
+                       //cleanup texture
+                       driver->removeTexture(rtt);
+
                        baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
 
                        if(image)
@@ -1518,6 +1673,86 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
                                image->drop();
                        }
                }
+               /*
+                       [lowpart:percent:filename
+                       Adds the lower part of a texture
+               */
+               else if(part_of_name.substr(0,9) == "[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;
+
+                       if(baseimg == NULL)
+                               baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
+                       video::IImage *img = sourcecache->getOrLoad(filename, device);
+                       if(img)
+                       {
+                               core::dimension2d<u32> dim = img->getDimension();
+                               core::position2d<s32> pos_base(0, 0);
+                               video::IImage *img2 =
+                                               driver->createImage(video::ECF_A8R8G8B8, dim);
+                               img->copyTo(img2);
+                               img->drop();
+                               core::position2d<s32> clippos(0, 0);
+                               clippos.Y = dim.Height * (100-percent) / 100;
+                               core::dimension2d<u32> clipdim = dim;
+                               clipdim.Height = clipdim.Height * percent / 100 + 1;
+                               core::rect<s32> cliprect(clippos, clipdim);
+                               img2->copyToWithAlpha(baseimg, pos_base,
+                                               core::rect<s32>(v2s32(0,0), dim),
+                                               video::SColor(255,255,255,255),
+                                               &cliprect);
+                               img2->drop();
+                       }
+               }
+               /*
+                       [verticalframe:N:I
+                       Crops a frame of a vertical animation.
+                       N = frame count, I = frame index
+               */
+               else if(part_of_name.substr(0,15) == "[verticalframe:")
+               {
+                       Strfnd sf(part_of_name);
+                       sf.next(":");
+                       u32 frame_count = stoi(sf.next(":"));
+                       u32 frame_index = stoi(sf.next(":"));
+
+                       if(baseimg == NULL){
+                               errorstream<<"generate_image(): baseimg!=NULL "
+                                               <<"for part_of_name=\""<<part_of_name
+                                               <<"\", cancelling."<<std::endl;
+                               return false;
+                       }
+                       
+                       v2u32 frame_size = baseimg->getDimension();
+                       frame_size.Y /= frame_count;
+
+                       video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
+                                       frame_size);
+                       if(!img){
+                               errorstream<<"generate_image(): Could not create image "
+                                               <<"for part_of_name=\""<<part_of_name
+                                               <<"\", cancelling."<<std::endl;
+                               return false;
+                       }
+
+                       // Fill target image with transparency
+                       img->fill(video::SColor(0,0,0,0));
+
+                       core::dimension2d<u32> dim = frame_size;
+                       core::position2d<s32> pos_dst(0, 0);
+                       core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
+                       baseimg->copyToWithAlpha(img, pos_dst,
+                                       core::rect<s32>(pos_src, dim),
+                                       video::SColor(255,255,255,255),
+                                       NULL);
+                       // Replace baseimg
+                       baseimg->drop();
+                       baseimg = img;
+               }
                else
                {
                        errorstream<<"generate_image(): Invalid "
@@ -1559,6 +1794,30 @@ void overlay(video::IImage *image, video::IImage *overlay)
        }
 }
 
+/*
+       Draw an image on top of an another one, using the alpha channel of the
+       source image
+
+       This exists because IImage::copyToWithAlpha() doesn't seem to always
+       work.
+*/
+static void blit_with_alpha(video::IImage *src, video::IImage *dst,
+               v2s32 src_pos, v2s32 dst_pos, v2u32 size)
+{
+       for(u32 y0=0; y0<size.Y; y0++)
+       for(u32 x0=0; x0<size.X; x0++)
+       {
+               s32 src_x = src_pos.X + x0;
+               s32 src_y = src_pos.Y + y0;
+               s32 dst_x = dst_pos.X + x0;
+               s32 dst_y = dst_pos.Y + y0;
+               video::SColor src_c = src->getPixel(src_x, src_y);
+               video::SColor dst_c = dst->getPixel(dst_x, dst_y);
+               dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
+               dst->setPixel(dst_x, dst_y, dst_c);
+       }
+}
+
 void brighten(video::IImage *image)
 {
        if(image == NULL)
@@ -1577,3 +1836,106 @@ void brighten(video::IImage *image)
        }
 }
 
+u32 parseImageTransform(const std::string& s)
+{
+       int total_transform = 0;
+
+       std::string transform_names[8];
+       transform_names[0] = "i";
+       transform_names[1] = "r90";
+       transform_names[2] = "r180";
+       transform_names[3] = "r270";
+       transform_names[4] = "fx";
+       transform_names[6] = "fy";
+
+       std::size_t pos = 0;
+       while(pos < s.size())
+       {
+               int transform = -1;
+               for(int i = 0; i <= 7; ++i)
+               {
+                       const std::string &name_i = transform_names[i];
+
+                       if(s[pos] == ('0' + i))
+                       {
+                               transform = i;
+                               pos++;
+                               break;
+                       }
+                       else if(!(name_i.empty()) &&
+                               lowercase(s.substr(pos, name_i.size())) == name_i)
+                       {
+                               transform = i;
+                               pos += name_i.size();
+                               break;
+                       }
+               }
+               if(transform < 0)
+                       break;
+
+               // Multiply total_transform and transform in the group D4
+               int new_total = 0;
+               if(transform < 4)
+                       new_total = (transform + total_transform) % 4;
+               else
+                       new_total = (transform - total_transform + 8) % 4;
+               if((transform >= 4) ^ (total_transform >= 4))
+                       new_total += 4;
+
+               total_transform = new_total;
+       }
+       return total_transform;
+}
+
+core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
+{
+       if(transform % 2 == 0)
+               return dim;
+       else
+               return core::dimension2d<u32>(dim.Height, dim.Width);
+}
+
+void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
+{
+       if(src == NULL || dst == NULL)
+               return;
+       
+       core::dimension2d<u32> srcdim = src->getDimension();
+       core::dimension2d<u32> dstdim = dst->getDimension();
+
+       assert(dstdim == imageTransformDimension(transform, srcdim));
+       assert(transform >= 0 && transform <= 7);
+
+       /*
+               Compute the transformation from source coordinates (sx,sy)
+               to destination coordinates (dx,dy).
+       */
+       int sxn = 0;
+       int syn = 2;
+       if(transform == 0)         // identity
+               sxn = 0, syn = 2;  //   sx = dx, sy = dy
+       else if(transform == 1)    // rotate by 90 degrees ccw
+               sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
+       else if(transform == 2)    // rotate by 180 degrees
+               sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
+       else if(transform == 3)    // rotate by 270 degrees ccw
+               sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
+       else if(transform == 4)    // flip x
+               sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
+       else if(transform == 5)    // flip x then rotate by 90 degrees ccw
+               sxn = 2, syn = 0;  //   sx = dy, sy = dx
+       else if(transform == 6)    // flip y
+               sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
+       else if(transform == 7)    // flip y then rotate by 90 degrees ccw
+               sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
+
+       for(u32 dy=0; dy<dstdim.Height; dy++)
+       for(u32 dx=0; dx<dstdim.Width; dx++)
+       {
+               u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
+               u32 sx = entries[sxn];
+               u32 sy = entries[syn];
+               video::SColor c = src->getPixel(sx,sy);
+               dst->setPixel(dx,dy,c);
+       }
+}