]> 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 1f1e9d38c60eadbe892f30891e55e222478de01a..4d2166342893fef5e70dc3ecfdd18dc94b74916c 100644 (file)
@@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #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"
@@ -194,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();
@@ -439,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(""));
@@ -461,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)
@@ -471,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
@@ -502,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);
        }
@@ -553,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,
@@ -604,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;
@@ -704,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);
@@ -936,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);
@@ -946,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 = ')';
 
@@ -958,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;
@@ -1026,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(): "
@@ -1097,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);
 
@@ -1173,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();
@@ -1228,7 +1272,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                }
                /*
                        [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 (str_starts_with(part_of_name, "[combine"))
                {
@@ -1236,20 +1280,19 @@ bool TextureSource::generateImagePart(std::string 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
@@ -1272,7 +1315,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
                }
                /*
-                       "[brighten"
+                       [brighten
                */
                else if (str_starts_with(part_of_name, "[brighten"))
                {
@@ -1286,7 +1329,7 @@ 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
@@ -1313,7 +1356,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
                }
                /*
-                       "[makealpha:R,G,B"
+                       [makealpha:R,G,B
                        Convert one color to transparent.
                */
                else if (str_starts_with(part_of_name, "[makealpha:"))
@@ -1329,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();
 
@@ -1353,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.
@@ -1521,12 +1563,11 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        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();
@@ -1606,9 +1647,9 @@ 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());
@@ -1639,28 +1680,22 @@ 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;
 
-                       core::dimension2d<u32> dim = baseimg->getDimension();
-                       video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
-
-                       if (!img) {
-                               errorstream << "generateImagePart(): Could not create image "
-                                               << "for part_of_name=\"" << part_of_name
-                                               << "\", cancelling." << std::endl;
-                               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();
+                       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.
@@ -1698,6 +1733,143 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                                }
                        }
                }
+               /*
+                       [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();
+
+                       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
+                                               << "\", cancelling." << std::endl;
+                               return false;
+                       }
+
+                       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
                {
                        errorstream << "generateImagePart(): Invalid "
@@ -1756,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.
@@ -1782,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
@@ -2057,7 +2271,7 @@ video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
 {
        std::string tname = "__shaderFlagsTexture";
        tname += normalmap_present ? "1" : "0";
-       
+
        if (isKnownSourceImage(tname)) {
                return getTexture(tname);
        } else {