X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient%2Ftile.cpp;h=4d2166342893fef5e70dc3ecfdd18dc94b74916c;hb=7057c196c442ff3484b53f48d940f4c9e0ffe23a;hp=7b326c15221d33e71ed0f96f5488b85b8f43c71d;hpb=837a2e1e5fef788cf108e036c27d092cedb3982f;p=minetest.git diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 7b326c152..4d2166342 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -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 @@ -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 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 newdim = core::dimension2d(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::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 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 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 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::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::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::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::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"<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="<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; iname); - 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 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(pos_from, dim), video::SColor(255,255,255,255), NULL);*/ - blit_with_alpha(image, baseimg, pos_from, pos_to, dim); + + core::dimension2d 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="< dim = img->getDimension(); infostream<<"Size "< 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 "<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 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 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 newdim = core::dimension2d(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 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 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 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 img_dim = baseimg->getDimension(); + core::dimension2d 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 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(0, 0), + texture->getOriginalSize()); + u32 total = 0; + u32 tR = 0; + u32 tG = 0; + u32 tB = 0; + core::dimension2d 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(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); + } +}