X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient%2Ftile.cpp;h=96312ea27c96322ee99ae024c58d4c14b43224b3;hb=a049e8267fabd101cb5c6528b3270214cb0647f0;hp=e5d02de7c9f26784be1bbed3e978407d5d983758;hpb=0d1eedcccc8b83fd5f5a9a75389fe8ac97d2c697;p=dragonfireclient.git diff --git a/src/client/tile.cpp b/src/client/tile.cpp index e5d02de7c..96312ea27 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -19,25 +19,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tile.h" +#include #include +#include #include "util/string.h" #include "util/container.h" #include "util/thread.h" -#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/string.h" // for parseColorString() - -#ifdef __ANDROID__ -#include -#endif +#include "util/strfnd.h" +#include "imagefilters.h" +#include "guiscalingfilter.h" +#include "renderingengine.h" /* A cache from texture name to texture path @@ -92,13 +87,13 @@ std::string getImagePath(std::string path) NULL }; // If there is no extension, add one - if (removeStringEnd(path, extensions) == "") + if (removeStringEnd(path, extensions).empty()) path = path + ".png"; // Check paths until something is found to exist const char **ext = extensions; do{ bool r = replace_ext(path, *ext); - if (r == false) + if (!r) return ""; if (fs::PathExists(path)) return path; @@ -118,9 +113,14 @@ std::string getImagePath(std::string path) Utilizes a thread-safe cache. */ -std::string getTexturePath(const std::string &filename) +std::string getTexturePath(const std::string &filename, bool *is_base_pack) { - std::string fullpath = ""; + std::string fullpath; + + // This can set a wrong value on cached textures, but is irrelevant because + // is_base_pack is only passed when initializing the textures the first time + if (is_base_pack) + *is_base_pack = false; /* Check from cache */ @@ -131,24 +131,27 @@ std::string getTexturePath(const std::string &filename) /* Check from texture_path */ - std::string texture_path = g_settings->get("texture_path"); - if (texture_path != "") - { - std::string testpath = texture_path + DIR_DELIM + filename; + for (const auto &path : getTextureDirs()) { + std::string testpath = path + DIR_DELIM; + testpath.append(filename); // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); + if (!fullpath.empty()) + break; } /* Check from default data directory */ - if (fullpath == "") + if (fullpath.empty()) { std::string base_path = porting::path_share + DIR_DELIM + "textures" + DIR_DELIM + "base" + DIR_DELIM + "pack"; std::string testpath = base_path + DIR_DELIM + filename; // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); + if (is_base_pack && !fullpath.empty()) + *is_base_pack = true; } // Add to cache (also an empty result is cached) @@ -190,16 +193,14 @@ class SourceImageCache { public: ~SourceImageCache() { - for (std::map::iterator iter = m_images.begin(); - iter != m_images.end(); iter++) { - iter->second->drop(); + for (auto &m_image : m_images) { + m_image.second->drop(); } m_images.clear(); } - void insert(const std::string &name, video::IImage *img, - bool prefer_local, video::IVideoDriver *driver) + void insert(const std::string &name, video::IImage *img, bool prefer_local) { - assert(img); + assert(img); // Pre-condition // Remove old image std::map::iterator n; n = m_images.find(name); @@ -212,10 +213,13 @@ class SourceImageCache bool need_to_grab = true; // Try to use local texture instead if asked to - if (prefer_local){ - std::string path = getTexturePath(name); - if (path != ""){ - video::IImage *img2 = driver->createImageFromFile(path.c_str()); + if (prefer_local) { + bool is_base_pack; + std::string path = getTexturePath(name, &is_base_pack); + // Ignore base pack + if (!path.empty() && !is_base_pack) { + video::IImage *img2 = RenderingEngine::get_video_driver()-> + createImageFromFile(path.c_str()); if (img2){ toadd = img2; need_to_grab = false; @@ -236,7 +240,7 @@ class SourceImageCache return NULL; } // Primarily fetches from cache, secondarily tries to read from filesystem - video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device) + video::IImage *getOrLoad(const std::string &name) { std::map::iterator n; n = m_images.find(name); @@ -244,9 +248,9 @@ class SourceImageCache n->second->grab(); // Grab for caller return n->second; } - video::IVideoDriver* driver = device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); std::string path = getTexturePath(name); - if (path == ""){ + if (path.empty()) { infostream<<"SourceImageCache::getOrLoad(): No path found for \"" < m_source_image_existence; @@ -400,7 +405,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; + std::mutex m_textureinfo_cache_mutex; // Queued texture fetches (to be processed by the main thread) RequestQueue m_get_texture_queue; @@ -409,63 +414,56 @@ class TextureSource : public IWritableTextureSource // but can't be deleted because the ITexture* might still be used std::vector m_texture_trash; + // Maps image file names to loaded palettes. + std::unordered_map m_palettes; + // Cached settings needed for making textures from meshes + bool m_setting_mipmap; bool m_setting_trilinear_filter; bool m_setting_bilinear_filter; - bool m_setting_anisotropic_filter; }; -IWritableTextureSource* createTextureSource(IrrlichtDevice *device) +IWritableTextureSource *createTextureSource() { - return new TextureSource(device); + return new TextureSource(); } -TextureSource::TextureSource(IrrlichtDevice *device): - m_device(device) +TextureSource::TextureSource() { - assert(m_device); - - m_main_thread = get_current_thread_id(); + m_main_thread = std::this_thread::get_id(); // Add a NULL TextureInfo as the first index, named "" - m_textureinfo_cache.push_back(TextureInfo("")); + m_textureinfo_cache.emplace_back(""); m_name_to_id[""] = 0; // Cache some settings // Note: Since this is only done once, the game must be restarted // for these settings to take effect + m_setting_mipmap = g_settings->getBool("mip_map"); m_setting_trilinear_filter = g_settings->getBool("trilinear_filter"); m_setting_bilinear_filter = g_settings->getBool("bilinear_filter"); - m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter"); } TextureSource::~TextureSource() { - video::IVideoDriver* driver = m_device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); unsigned int textures_before = driver->getTextureCount(); - for (std::vector::iterator iter = - m_textureinfo_cache.begin(); - iter != m_textureinfo_cache.end(); iter++) - { + for (const auto &iter : m_textureinfo_cache) { //cleanup texture - if (iter->texture) - driver->removeTexture(iter->texture); + if (iter.texture) + driver->removeTexture(iter.texture); } m_textureinfo_cache.clear(); - for (std::vector::iterator iter = - m_texture_trash.begin(); iter != m_texture_trash.end(); - iter++) { - video::ITexture *t = *iter; - + for (auto t : m_texture_trash) { //cleanup trashed texture driver->removeTexture(t); } - infostream << "~TextureSource() "<< textures_before << "/" - << driver->getTextureCount() << std::endl; + infostream << "~TextureSource() before cleanup: "<< textures_before + << " after: " << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) @@ -476,7 +474,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()) @@ -488,43 +486,35 @@ u32 TextureSource::getTextureId(const std::string &name) /* Get texture */ - if (get_current_thread_id() == m_main_thread) - { + if (std::this_thread::get_id() == m_main_thread) { return generateTexture(name); } - else - { - infostream<<"getTextureId(): Queued: name=\""< result_queue; - // Throw a request in - m_get_texture_queue.add(name, 0, 0, &result_queue); + infostream<<"getTextureId(): Queued: name=\""< result_queue; - try - { - while(true) { - // Wait result for a second - GetResult - result = result_queue.pop_front(1000); + // Throw a request in + m_get_texture_queue.add(name, 0, 0, &result_queue); - if (result.key == name) { - return result.item; - } + try { + while(true) { + // Wait result for a second + GetResult + result = result_queue.pop_front(1000); + + if (result.key == name) { + return result.item; } } - catch(ItemNotFoundException &e) - { - errorstream<<"Waiting for texture " << name << " timed out."<::iterator n; n = m_name_to_id.find(name); if (n != m_name_to_id.end()) { @@ -590,25 +586,26 @@ u32 TextureSource::generateTexture(const std::string &name) /* Calling only allowed from main thread */ - if (get_current_thread_id() != m_main_thread) { + if (std::this_thread::get_id() != m_main_thread) { errorstream<<"TextureSource::generateTexture() " "called not from main thread"<getVideoDriver(); - assert(driver); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + sanity_check(driver); video::IImage *img = generateImage(name); video::ITexture *tex = NULL; if (img != NULL) { -#ifdef __ANDROID__ +#if ENABLE_GLES img = Align2Npot2(img, driver); #endif // Create texture from resulting image tex = driver->addTexture(name.c_str(), img); + guiScalingCache(io::path(name.c_str()), driver, img); img->drop(); } @@ -616,7 +613,7 @@ u32 TextureSource::generateTexture(const std::string &name) 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); @@ -628,7 +625,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()) { @@ -643,7 +640,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; @@ -660,6 +657,73 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) return getTexture(actual_id); } +video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) +{ + static thread_local bool filter_needed = + g_settings->getBool("texture_clean_transparent") || m_setting_mipmap || + ((m_setting_trilinear_filter || m_setting_bilinear_filter) && + g_settings->getS32("texture_min_size") > 1); + // Avoid duplicating texture if it won't actually change + if (filter_needed) + return getTexture(name + "^[applyfiltersformesh", id); + return getTexture(name, id); +} + +Palette* TextureSource::getPalette(const std::string &name) +{ + // Only the main thread may load images + sanity_check(std::this_thread::get_id() == m_main_thread); + + if (name.empty()) + return NULL; + + auto it = m_palettes.find(name); + if (it == m_palettes.end()) { + // Create palette + video::IImage *img = generateImage(name); + if (!img) { + warningstream << "TextureSource::getPalette(): palette \"" << name + << "\" could not be loaded." << std::endl; + return NULL; + } + Palette new_palette; + u32 w = img->getDimension().Width; + u32 h = img->getDimension().Height; + // Real area of the image + u32 area = h * w; + if (area == 0) + return NULL; + if (area > 256) { + warningstream << "TextureSource::getPalette(): the specified" + << " palette image \"" << name << "\" is larger than 256" + << " pixels, using the first 256." << std::endl; + area = 256; + } else if (256 % area != 0) + warningstream << "TextureSource::getPalette(): the " + << "specified palette image \"" << name << "\" does not " + << "contain power of two pixels." << std::endl; + // We stretch the palette so it will fit 256 values + // This many param2 values will have the same color + u32 step = 256 / area; + // For each pixel in the image + for (u32 i = 0; i < area; i++) { + video::SColor c = img->getPixel(i % w, i / w); + // Fill in palette with 'step' colors + for (u32 j = 0; j < step; j++) + new_palette.push_back(c); + } + img->drop(); + // Fill in remaining elements + while (new_palette.size() < 256) + new_palette.emplace_back(0xFFFFFFFF); + m_palettes[name] = new_palette; + it = m_palettes.find(name); + } + if (it != m_palettes.end()) + return &((*it).second); + return NULL; +} + void TextureSource::processQueue() { /* @@ -684,250 +748,165 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im { //infostream<<"TextureSource::insertSourceImage(): name="<getVideoDriver()); + m_sourcecache.insert(name, img, true); m_source_image_existence.set(name, true); } void TextureSource::rebuildImagesAndTextures() { - JMutexAutoLock lock(m_textureinfo_cache_mutex); + MutexAutoLock lock(m_textureinfo_cache_mutex); - video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver != 0); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + sanity_check(driver); + + infostream << "TextureSource: recreating " << m_textureinfo_cache.size() + << " textures" << std::endl; // Recreate textures - for (u32 i=0; iname); -#ifdef __ANDROID__ + for (TextureInfo &ti : m_textureinfo_cache) { + video::IImage *img = generateImage(ti.name); +#if ENABLE_GLES img = Align2Npot2(img, driver); - assert(img->getDimension().Height == npot2(img->getDimension().Height)); - assert(img->getDimension().Width == npot2(img->getDimension().Width)); #endif // Create texture from resulting image video::ITexture *t = NULL; if (img) { - t = driver->addTexture(ti->name.c_str(), img); + t = driver->addTexture(ti.name.c_str(), img); + guiScalingCache(io::path(ti.name.c_str()), driver, img); img->drop(); } - video::ITexture *t_old = ti->texture; + video::ITexture *t_old = ti.texture; // Replace texture - ti->texture = t; + ti.texture = t; if (t_old) m_texture_trash.push_back(t_old); } } -video::ITexture* TextureSource::generateTextureFromMesh( - const TextureFromMeshParams ¶ms) +inline static void applyShadeFactor(video::SColor &color, u32 factor) { - video::IVideoDriver *driver = m_device->getVideoDriver(); - assert(driver); - -#ifdef __ANDROID__ - const GLubyte* renderstr = glGetString(GL_RENDERER); - std::string renderer((char*) renderstr); - - // use no render to texture hack - if ( - (renderer.find("Adreno") != std::string::npos) || - (renderer.find("Mali") != std::string::npos) || - (renderer.find("Immersion") != std::string::npos) || - (renderer.find("Tegra") != std::string::npos) || - g_settings->getBool("inventory_image_hack") - ) { - // Get a scene manager - scene::ISceneManager *smgr_main = m_device->getSceneManager(); - assert(smgr_main); - scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); - assert(smgr); - - const float scaling = 0.2; - - scene::IMeshSceneNode* meshnode = - smgr->addMeshSceneNode(params.mesh, NULL, - -1, v3f(0,0,0), v3f(0,0,0), - v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true); - meshnode->setMaterialFlag(video::EMF_LIGHTING, true); - meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); - meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); - meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); - meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); - - scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, - params.camera_position, params.camera_lookat); - // second parameter of setProjectionMatrix (isOrthogonal) is ignored - camera->setProjectionMatrix(params.camera_projection_matrix, false); - - smgr->setAmbientLight(params.ambient_light); - smgr->addLightSceneNode(0, - params.light_position, - params.light_color, - params.light_radius*scaling); - - core::dimension2d screen = driver->getScreenSize(); - - // Render scene - driver->beginScene(true, true, video::SColor(0,0,0,0)); - driver->clearZBuffer(); - smgr->drawAll(); - - core::dimension2d partsize(screen.Width * scaling,screen.Height * scaling); - - irr::video::IImage* rawImage = - driver->createImage(irr::video::ECF_A8R8G8B8, partsize); - - u8* pixels = static_cast(rawImage->lock()); - if (!pixels) - { - rawImage->drop(); - return NULL; - } - - core::rect source( - screen.Width /2 - (screen.Width * (scaling / 2)), - screen.Height/2 - (screen.Height * (scaling / 2)), - screen.Width /2 + (screen.Width * (scaling / 2)), - screen.Height/2 + (screen.Height * (scaling / 2)) - ); - - glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y, - partsize.Width, partsize.Height, GL_RGBA, - GL_UNSIGNED_BYTE, pixels); - - driver->endScene(); - - // Drop scene manager - smgr->drop(); - - unsigned int pixelcount = partsize.Width*partsize.Height; - - u8* runptr = pixels; - for (unsigned int i=0; i < pixelcount; i++) { - - u8 B = *runptr; - u8 G = *(runptr+1); - u8 R = *(runptr+2); - u8 A = *(runptr+3); - - //BGRA -> RGBA - *runptr = R; - runptr ++; - *runptr = G; - runptr ++; - *runptr = B; - runptr ++; - *runptr = A; - runptr ++; - } - - video::IImage* inventory_image = - driver->createImage(irr::video::ECF_A8R8G8B8, params.dim); - - rawImage->copyToScaling(inventory_image); - rawImage->drop(); - - video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); - inventory_image->drop(); + u32 f = core::clamp(factor, 0, 256); + color.setRed(color.getRed() * f / 256); + color.setGreen(color.getGreen() * f / 256); + color.setBlue(color.getBlue() * f / 256); +} - if (rtt == NULL) { - errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl; - return NULL; +static video::IImage *createInventoryCubeImage( + video::IImage *top, video::IImage *left, video::IImage *right) +{ + core::dimension2du size_top = top->getDimension(); + core::dimension2du size_left = left->getDimension(); + core::dimension2du size_right = right->getDimension(); + + u32 size = npot2(std::max({ + size_top.Width, size_top.Height, + size_left.Width, size_left.Height, + size_right.Width, size_right.Height, + })); + + // It must be divisible by 4, to let everything work correctly. + // But it is a power of 2, so being at least 4 is the same. + // And the resulting texture should't be too large as well. + size = core::clamp(size, 4, 64); + + // With such parameters, the cube fits exactly, touching each image line + // from `0` to `cube_size - 1`. (Note that division is exact here). + u32 cube_size = 9 * size; + u32 offset = size / 2; + + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + + auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * { + image->grab(); + core::dimension2du dim = image->getDimension(); + video::ECOLOR_FORMAT format = image->getColorFormat(); + if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) { + video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size}); + image->copyToScaling(scaled); + image->drop(); + image = scaled; } + sanity_check(image->getPitch() == 4 * size); + return reinterpret_cast(image->getData()); + }; + auto free_image = [] (video::IImage *image) -> void { + image->drop(); + }; - driver->makeColorKeyTexture(rtt, v2s32(0,0)); - - if (params.delete_texture_on_shutdown) - m_texture_trash.push_back(rtt); - - return rtt; - } -#endif - - if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false) - { - static bool warned = false; - if (!warned) - { - errorstream<<"TextureSource::generateTextureFromMesh(): " - <<"EVDF_RENDER_TO_TARGET not supported."<createImage(video::ECF_A8R8G8B8, {cube_size, cube_size}); + sanity_check(result->getPitch() == 4 * cube_size); + result->fill(video::SColor(0x00000000u)); + u32 *target = reinterpret_cast(result->getData()); + + // Draws single cube face + // `shade_factor` is face brightness, in range [0.0, 1.0] + // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix + // `offsets` list pixels to be drawn for single source pixel + auto draw_image = [=] (video::IImage *image, float shade_factor, + s16 xu, s16 xv, s16 x1, + s16 yu, s16 yv, s16 y1, + std::initializer_list offsets) -> void { + u32 brightness = core::clamp(256 * shade_factor, 0, 256); + const u32 *source = lock_image(image); + for (u16 v = 0; v < size; v++) { + for (u16 u = 0; u < size; u++) { + video::SColor pixel(*source); + applyShadeFactor(pixel, brightness); + s16 x = xu * u + xv * v + x1; + s16 y = yu * u + yv * v + y1; + for (const auto &off : offsets) + target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color; + source++; + } } - return NULL; - } - - // Create render target texture - video::ITexture *rtt = driver->addRenderTargetTexture( - params.dim, params.rtt_texture_name.c_str(), - video::ECF_A8R8G8B8); - if (rtt == NULL) - { - errorstream<<"TextureSource::generateTextureFromMesh(): " - <<"addRenderTargetTexture returned NULL."<setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) { - driver->removeTexture(rtt); - errorstream<<"TextureSource::generateTextureFromMesh(): " - <<"failed to set render target"<getSceneManager(); - assert(smgr_main); - scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); - assert(smgr); - - scene::IMeshSceneNode* meshnode = - smgr->addMeshSceneNode(params.mesh, NULL, - -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true); - meshnode->setMaterialFlag(video::EMF_LIGHTING, true); - meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); - meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); - meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); - meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); - - scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, - params.camera_position, params.camera_lookat); - // second parameter of setProjectionMatrix (isOrthogonal) is ignored - camera->setProjectionMatrix(params.camera_projection_matrix, false); - - smgr->setAmbientLight(params.ambient_light); - smgr->addLightSceneNode(0, - params.light_position, - params.light_color, - params.light_radius); - - // Render scene - driver->beginScene(true, true, video::SColor(0,0,0,0)); - smgr->drawAll(); - driver->endScene(); - - // Drop scene manager - smgr->drop(); - - // Unset render target - driver->setRenderTarget(0, false, true, 0); - - if (params.delete_texture_on_shutdown) - m_texture_trash.push_back(rtt); - - return rtt; + draw_image(top, 1.000000f, + 4, -4, 4 * (size - 1), + 2, 2, 0, + { + {2, 0}, {3, 0}, {4, 0}, {5, 0}, + {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, + {2, 2}, {3, 2}, {4, 2}, {5, 2}, + }); + + draw_image(left, 0.836660f, + 4, 0, 0, + 2, 5, 2 * size, + { + {0, 0}, {1, 0}, + {0, 1}, {1, 1}, {2, 1}, {3, 1}, + {0, 2}, {1, 2}, {2, 2}, {3, 2}, + {0, 3}, {1, 3}, {2, 3}, {3, 3}, + {0, 4}, {1, 4}, {2, 4}, {3, 4}, + {2, 5}, {3, 5}, + }); + + draw_image(right, 0.670820f, + 4, 0, 4 * size, + -2, 5, 4 * size - 2, + { + {2, 0}, {3, 0}, + {0, 1}, {1, 1}, {2, 1}, {3, 1}, + {0, 2}, {1, 2}, {2, 2}, {3, 2}, + {0, 3}, {1, 3}, {2, 3}, {3, 3}, + {0, 4}, {1, 4}, {2, 4}, {3, 4}, + {0, 5}, {1, 5}, + }); + + return result; } 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 = ')'; @@ -935,7 +914,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; @@ -976,10 +957,6 @@ video::IImage* TextureSource::generateImage(const std::string &name) baseimg = generateImage(name.substr(0, last_separator_pos)); } - - video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver); - /* Parse out the last part of the name of the image and act according to it @@ -987,7 +964,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 */ @@ -1003,10 +980,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(): " @@ -1023,90 +1002,97 @@ video::IImage* TextureSource::generateImage(const std::string &name) return baseimg; } -#ifdef __ANDROID__ -#include +#if ENABLE_GLES + /** * Check and align image to npot2 if required by hardware * @param image image to check for npot2 alignment * @param driver driver to use for image operations * @return image or copy of image aligned to npot2 */ -video::IImage * Align2Npot2(video::IImage * image, - video::IVideoDriver* driver) +video::IImage *Align2Npot2(video::IImage *image, + video::IVideoDriver *driver) { - if (image == NULL) { + if (image == NULL) return image; - } - core::dimension2d dim = image->getDimension(); - - std::string extensions = (char*) glGetString(GL_EXTENSIONS); - if (extensions.find("GL_OES_texture_npot") != std::string::npos) { + if (driver->queryFeature(video::EVDF_TEXTURE_NPOT)) return image; - } + core::dimension2d dim = image->getDimension(); unsigned int height = npot2(dim.Height); unsigned int width = npot2(dim.Width); - if ((dim.Height == height) && - (dim.Width == width)) { + if (dim.Height == height && dim.Width == width) return image; - } - if (dim.Height > height) { + if (dim.Height > height) height *= 2; - } - - if (dim.Width > width) { + if (dim.Width > width) width *= 2; - } video::IImage *targetimage = driver->createImage(video::ECF_A8R8G8B8, core::dimension2d(width, height)); - if (targetimage != NULL) { + if (targetimage != NULL) image->copyToScaling(targetimage); - } image->drop(); return targetimage; } #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) { - video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver); + const char escape = '\\'; // same as in generateImage() + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + sanity_check(driver); // Stuff starting with [ are special commands - if (part_of_name.size() == 0 || part_of_name[0] != '[') - { - video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); -#ifdef __ANDROID__ + if (part_of_name.empty() || part_of_name[0] != '[') { + video::IImage *image = m_sourcecache.getOrLoad(part_of_name); +#if ENABLE_GLES image = Align2Npot2(image, driver); #endif if (image == NULL) { - if (part_of_name != "") { - if (part_of_name.find("_normal.png") == std::string::npos){ - errorstream<<"generateImage(): Could not load image \"" - < dim(2,2); core::dimension2d dim(1,1); image = driver->createImage(video::ECF_A8R8G8B8, dim); - assert(image); + sanity_check(image != NULL); /*image->setPixel(0,0, video::SColor(255,255,0,0)); image->setPixel(1,0, video::SColor(255,0,255,0)); image->setPixel(0,1, video::SColor(255,0,0,255)); @@ -1150,7 +1136,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 = RenderingEngine::get_video_driver()-> + 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 = RenderingEngine::get_video_driver()-> + 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(); @@ -1169,7 +1176,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 " @@ -1179,57 +1186,66 @@ bool TextureSource::generateImagePart(std::string part_of_name, } // Crack image number and overlay option + // Format: crack[o][:]:: bool use_overlay = (part_of_name[6] == 'o'); Strfnd sf(part_of_name); sf.next(":"); s32 frame_count = stoi(sf.next(":")); s32 progression = stoi(sf.next(":")); + s32 tiles = 1; + // Check whether there is the argument, that is, + // whether there are 3 arguments. If so, shift values + // as the first and not the last argument is optional. + auto s = sf.next(":"); + if (!s.empty()) { + tiles = frame_count; + frame_count = progression; + progression = stoi(s); + } - /* - 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( - "crack_anylength.png", m_device); + It is an image with a number of cracking stages + horizontally tiled. + */ + video::IImage *img_crack = m_sourcecache.getOrLoad( + "crack_anylength.png"); - 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(); + progression, driver, tiles); + 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 "< pos_base(x, y); video::IImage *img2 = driver->createImage(video::ECF_A8R8G8B8, dim); @@ -1248,9 +1264,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, } } /* - "[brighten" + [brighten */ - else if (part_of_name.substr(0,9) == "[brighten") + else if (str_starts_with(part_of_name, "[brighten")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg==NULL " @@ -1262,13 +1278,13 @@ bool TextureSource::generateImagePart(std::string part_of_name, brighten(baseimg); } /* - "[noalpha" + [noalpha Make image completely opaque. Used for the leaves texture when in old leaves mode, so that the transparent parts don't look completely black when simple alpha channel is used for rendering. */ - else if (part_of_name.substr(0,8) == "[noalpha") + else if (str_starts_with(part_of_name, "[noalpha")) { if (baseimg == NULL){ errorstream<<"generateImagePart(): baseimg==NULL " @@ -1289,10 +1305,10 @@ bool TextureSource::generateImagePart(std::string part_of_name, } } /* - "[makealpha:R,G,B" + [makealpha:R,G,B Convert one color to transparent. */ - else if (part_of_name.substr(0,11) == "[makealpha:") + else if (str_starts_with(part_of_name, "[makealpha:")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " @@ -1305,7 +1321,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 dim = baseimg->getDimension(); @@ -1329,7 +1344,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. @@ -1348,7 +1363,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 " @@ -1362,7 +1377,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, transform, baseimg->getDimension()); video::IImage *image = driver->createImage( baseimg->getColorFormat(), dim); - assert(image); + sanity_check(image != NULL); imageTransform(transform, baseimg, image); baseimg->drop(); baseimg = image; @@ -1375,7 +1390,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 " @@ -1404,105 +1419,29 @@ bool TextureSource::generateImagePart(std::string part_of_name, return true; } -#ifdef __ANDROID__ - assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height)); - assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width)); - - assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height)); - assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width)); - - assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height)); - assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width)); -#endif + baseimg = createInventoryCubeImage(img_top, img_left, img_right); - // Create textures from images - video::ITexture *texture_top = driver->addTexture( - (imagename_top + "__temp__").c_str(), img_top); - video::ITexture *texture_left = driver->addTexture( - (imagename_left + "__temp__").c_str(), img_left); - video::ITexture *texture_right = driver->addTexture( - (imagename_right + "__temp__").c_str(), img_right); - assert(texture_top && texture_left && texture_right); - - // Drop images + // Face images are not needed anymore img_top->drop(); img_left->drop(); img_right->drop(); - /* - Draw a cube mesh into a render target texture - */ - scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1)); - setMeshColor(cube, video::SColor(255, 255, 255, 255)); - cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top); - cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top); - cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right); - cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right); - cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left); - cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left); - - TextureFromMeshParams params; - params.mesh = cube; - params.dim.set(64, 64); - params.rtt_texture_name = part_of_name + "_RTT"; - // We will delete the rtt texture ourselves - params.delete_texture_on_shutdown = false; - params.camera_position.set(0, 1.0, -1.5); - params.camera_position.rotateXZBy(45); - params.camera_lookat.set(0, 0, 0); - // Set orthogonal projection - params.camera_projection_matrix.buildProjectionMatrixOrthoLH( - 1.65, 1.65, 0, 100); - - params.ambient_light.set(1.0, 0.2, 0.2, 0.2); - params.light_position.set(10, 100, -50); - params.light_color.set(1.0, 0.5, 0.5, 0.5); - params.light_radius = 1000; - - video::ITexture *rtt = generateTextureFromMesh(params); - - // Drop mesh - cube->drop(); - - // Free textures - driver->removeTexture(texture_top); - driver->removeTexture(texture_left); - driver->removeTexture(texture_right); - - if (rtt == NULL) { - baseimg = generateImage(imagename_top); - return true; - } - - // Create image of render target - video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim); - assert(image); - - // Cleanup texture - driver->removeTexture(rtt); - - baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim); - - if (image) { - image->copyTo(baseimg); - image->drop(); - } + return true; } /* [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(); @@ -1528,7 +1467,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(":"); @@ -1572,7 +1511,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 " @@ -1582,23 +1521,49 @@ 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 << "\"."; } } + /* + [multiply:color + multiplys a given color to any pixel of an image + color = color as ColorString + */ + else if (str_starts_with(part_of_name, "[multiply:")) { + Strfnd sf(part_of_name); + sf.next(":"); + std::string color_str = sf.next(":"); + + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg != NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + video::SColor color; + + if (!parseColorString(color_str, color, false)) + return false; + + apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color); + } /* [colorize:color 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(":"); @@ -1613,16 +1578,196 @@ 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")) + { + /* IMPORTANT: When changing this, getTextureForMesh() needs to be + * updated too. */ + + if (!baseimg) { + errorstream << "generateImagePart(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + // Apply the "clean transparent" filter, if needed + if (m_setting_mipmap || 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. + */ + const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter; + const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1; + 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. + */ + if ((dim.Width == 0) || (dim.Height == 0)) { + errorstream << "generateImagePart(): Illegal 0 dimension " + << "for part_of_name=\""<< part_of_name + << "\", cancelling." << std::endl; + return false; + } + 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 = RenderingEngine::get_video_driver()-> + 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 @@ -1630,10 +1775,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 { @@ -1645,6 +1795,24 @@ bool TextureSource::generateImagePart(std::string part_of_name, return true; } +/* + Calculate the color of a single pixel drawn on top of another pixel. + + This is a little more complicated than just video::SColor::getInterpolated + because getInterpolated does not handle alpha correctly. For example, a + pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a + pixel with alpha=160, while getInterpolated would yield alpha=96. +*/ +static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio) +{ + if (dst_c.getAlpha() == 0) + return src_c; + video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f); + out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) * + src_c.getAlpha() * ratio / (255 * 255)); + return out_c; +} + /* Draw an image on top of an another one, using the alpha channel of the source image @@ -1664,7 +1832,7 @@ static void blit_with_alpha(video::IImage *src, video::IImage *dst, 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_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } @@ -1687,12 +1855,15 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, video::SColor dst_c = dst->getPixel(dst_x, dst_y); if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) { - dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } } +// 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. @@ -1719,6 +1890,66 @@ 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, + const 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 color to destination +*/ +static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, + const video::SColor &color) +{ + video::SColor dst_c; + + 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); + dst_c.set( + dst_c.getAlpha(), + (dst_c.getRed() * color.getRed()) / 255, + (dst_c.getGreen() * color.getGreen()) / 255, + (dst_c.getBlue() * color.getBlue()) / 255 + ); + dst->setPixel(x, y, dst_c); + } +} /* Apply mask to destination @@ -1740,74 +1971,78 @@ static void apply_mask(video::IImage *mask, video::IImage *dst, } } +video::IImage *create_crack_image(video::IImage *crack, s32 frame_index, + core::dimension2d size, u8 tiles, video::IVideoDriver *driver) +{ + core::dimension2d strip_size = crack->getDimension(); + core::dimension2d frame_size(strip_size.Width, strip_size.Width); + core::dimension2d tile_size(size / tiles); + s32 frame_count = strip_size.Height / strip_size.Width; + if (frame_index >= frame_count) + frame_index = frame_count - 1; + core::rect frame(v2s32(0, frame_index * frame_size.Height), frame_size); + video::IImage *result = nullptr; + +// extract crack frame + video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size); + if (!crack_tile) + return nullptr; + if (tile_size == frame_size) { + crack->copyTo(crack_tile, v2s32(0, 0), frame); + } else { + video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size); + if (!crack_frame) + goto exit__has_tile; + crack->copyTo(crack_frame, v2s32(0, 0), frame); + crack_frame->copyToScaling(crack_tile); + crack_frame->drop(); + } + if (tiles == 1) + return crack_tile; + +// tile it + result = driver->createImage(video::ECF_A8R8G8B8, size); + if (!result) + goto exit__has_tile; + result->fill({}); + for (u8 i = 0; i < tiles; i++) + for (u8 j = 0; j < tiles; j++) + crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height)); + +exit__has_tile: + crack_tile->drop(); + return result; +} + static void draw_crack(video::IImage *crack, video::IImage *dst, bool use_overlay, s32 frame_count, s32 progression, - video::IVideoDriver *driver) + video::IVideoDriver *driver, u8 tiles) { // Dimension of destination image core::dimension2d dim_dst = dst->getDimension(); - // Dimension of original image - core::dimension2d dim_crack = crack->getDimension(); - // Count of crack stages - s32 crack_count = dim_crack.Height / dim_crack.Width; // Limit frame_count if (frame_count > (s32) dim_dst.Height) frame_count = dim_dst.Height; if (frame_count < 1) frame_count = 1; - // Limit progression - if (progression > crack_count-1) - progression = crack_count-1; - // Dimension of a single crack stage - core::dimension2d dim_crack_cropped( - dim_crack.Width, - dim_crack.Width - ); // Dimension of the scaled crack stage, // which is the same as the dimension of a single destination frame - core::dimension2d dim_crack_scaled( + core::dimension2d frame_size( dim_dst.Width, dim_dst.Height / frame_count ); - // Create cropped and scaled crack images - video::IImage *crack_cropped = driver->createImage( - video::ECF_A8R8G8B8, dim_crack_cropped); - video::IImage *crack_scaled = driver->createImage( - video::ECF_A8R8G8B8, dim_crack_scaled); + video::IImage *crack_scaled = create_crack_image(crack, progression, + frame_size, tiles, driver); + if (!crack_scaled) + return; - if (crack_cropped && crack_scaled) - { - // Crop crack image - v2s32 pos_crack(0, progression*dim_crack.Width); - crack->copyTo(crack_cropped, - v2s32(0,0), - core::rect(pos_crack, dim_crack_cropped)); - // Scale crack image by copying - crack_cropped->copyToScaling(crack_scaled); - // Copy or overlay crack image onto each frame - for (s32 i = 0; i < frame_count; ++i) - { - v2s32 dst_pos(0, dim_crack_scaled.Height * i); - if (use_overlay) - { - blit_with_alpha_overlay(crack_scaled, dst, - v2s32(0,0), dst_pos, - dim_crack_scaled); - } - else - { - blit_with_alpha(crack_scaled, dst, - v2s32(0,0), dst_pos, - dim_crack_scaled); - } - } + auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha; + for (s32 i = 0; i < frame_count; ++i) { + v2s32 dst_pos(0, frame_size.Height * i); + blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size); } - if (crack_scaled) - crack_scaled->drop(); - - if (crack_cropped) - crack_cropped->drop(); + crack_scaled->drop(); } void brighten(video::IImage *image) @@ -1854,9 +2089,8 @@ u32 parseImageTransform(const std::string& s) pos++; break; } - else if (!(name_i.empty()) && - lowercase(s.substr(pos, name_i.size())) == name_i) - { + + if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) { transform = i; pos += name_i.size(); break; @@ -1883,8 +2117,8 @@ core::dimension2d imageTransformDimension(u32 transform, core::dimension2d< { if (transform % 2 == 0) return dim; - else - return core::dimension2d(dim.Height, dim.Width); + + return core::dimension2d(dim.Height, dim.Width); } void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) @@ -1892,10 +2126,10 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) if (src == NULL || dst == NULL) return; - core::dimension2d srcdim = src->getDimension(); core::dimension2d dstdim = dst->getDimension(); - assert(dstdim == imageTransformDimension(transform, srcdim)); + // Pre-conditions + assert(dstdim == imageTransformDimension(transform, src->getDimension())); assert(transform <= 7); /* @@ -1934,21 +2168,90 @@ 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("."); + static const char *normal_ext = "_normal.png"; + static const u32 normal_ext_size = strlen(normal_ext); + size_t pos = fname_base.find('.'); std::string fname_normal = fname_base.substr(0, pos) + normal_ext; if (isKnownSourceImage(fname_normal)) { // look for image extension and replace it size_t i = 0; - while ((i = fname_base.find(".", i)) != std::string::npos) { + while ((i = fname_base.find('.', i)) != std::string::npos) { fname_base.replace(i, 4, normal_ext); - i += normal_ext.length(); - } - return getTexture(fname_base, &id); + i += normal_ext_size; } + return getTexture(fname_base); + } return NULL; } + +video::SColor TextureSource::getTextureAverageColor(const std::string &name) +{ + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + video::SColor c(0, 0, 0, 0); + video::ITexture *texture = getTexture(name); + if (!texture) + return c; + video::IImage *image = driver->createImage(texture, + core::position2d(0, 0), + texture->getOriginalSize()); + if (!image) + return c; + + 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); + } + + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + 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); + +} + +std::vector getTextureDirs() +{ + return fs::GetRecursiveDirs(g_settings->get("texture_path")); +}