3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "main.h" // for g_settings
26 #include <ICameraSceneNode.h>
28 #include "mapnode.h" // For texture atlas making
29 #include "nodedef.h" // For texture atlas making
31 #include "util/string.h"
32 #include "util/container.h"
33 #include "util/thread.h"
34 #include "util/numeric.h"
37 A cache from texture name to texture path
39 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
42 Replaces the filename extension.
44 std::string image = "a/image.png"
45 replace_ext(image, "jpg")
46 -> image = "a/image.jpg"
47 Returns true on success.
49 static bool replace_ext(std::string &path, const char *ext)
53 // Find place of last dot, fail if \ or / found.
55 for(s32 i=path.size()-1; i>=0; i--)
63 if(path[i] == '\\' || path[i] == '/')
66 // If not found, return an empty string
69 // Else make the new path
70 path = path.substr(0, last_dot_i+1) + ext;
75 Find out the full path of an image by trying different filename
80 static std::string getImagePath(std::string path)
82 // A NULL-ended list of possible image extensions
83 const char *extensions[] = {
84 "png", "jpg", "bmp", "tga",
85 "pcx", "ppm", "psd", "wal", "rgb",
88 // If there is no extension, add one
89 if(removeStringEnd(path, extensions) == "")
91 // Check paths until something is found to exist
92 const char **ext = extensions;
94 bool r = replace_ext(path, *ext);
97 if(fs::PathExists(path))
100 while((++ext) != NULL);
106 Gets the path to a texture by first checking if the texture exists
107 in texture_path and if not, using the data path.
109 Checks all supported extensions by replacing the original extension.
111 If not found, returns "".
113 Utilizes a thread-safe cache.
115 std::string getTexturePath(const std::string &filename)
117 std::string fullpath = "";
121 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
126 Check from texture_path
128 std::string texture_path = g_settings->get("texture_path");
129 if(texture_path != "")
131 std::string testpath = texture_path + DIR_DELIM + filename;
132 // Check all filename extensions. Returns "" if not found.
133 fullpath = getImagePath(testpath);
137 Check from $user/textures/all
141 std::string texture_path = porting::path_user + DIR_DELIM
142 + "textures" + DIR_DELIM + "all";
143 std::string testpath = texture_path + DIR_DELIM + filename;
144 // Check all filename extensions. Returns "" if not found.
145 fullpath = getImagePath(testpath);
149 Check from default data directory
153 std::string base_path = porting::path_share + DIR_DELIM + "textures"
154 + DIR_DELIM + "base" + DIR_DELIM + "pack";
155 std::string testpath = base_path + DIR_DELIM + filename;
156 // Check all filename extensions. Returns "" if not found.
157 fullpath = getImagePath(testpath);
160 // Add to cache (also an empty result is cached)
161 g_texturename_to_path_cache.set(filename, fullpath);
168 An internal variant of AtlasPointer with more data.
169 (well, more like a wrapper)
172 struct SourceAtlasPointer
176 video::IImage *atlas_img; // The source image of the atlas
177 // Integer variants of position and size
182 const std::string &name_,
183 AtlasPointer a_=AtlasPointer(0, NULL),
184 video::IImage *atlas_img_=NULL,
185 v2s32 intpos_=v2s32(0,0),
186 v2u32 intsize_=v2u32(0,0)
190 atlas_img(atlas_img_),
198 SourceImageCache: A cache used for storing source images.
201 class SourceImageCache
204 void insert(const std::string &name, video::IImage *img,
205 bool prefer_local, video::IVideoDriver *driver)
209 std::map<std::string, video::IImage*>::iterator n;
210 n = m_images.find(name);
211 if(n != m_images.end()){
212 video::IImage *oldimg = n->second;
216 // Try to use local texture instead if asked to
218 std::string path = getTexturePath(name.c_str());
220 video::IImage *img2 = driver->createImageFromFile(path.c_str());
222 m_images[name] = img2;
228 m_images[name] = img;
230 video::IImage* get(const std::string &name)
232 std::map<std::string, video::IImage*>::iterator n;
233 n = m_images.find(name);
234 if(n != m_images.end())
238 // Primarily fetches from cache, secondarily tries to read from filesystem
239 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
241 std::map<std::string, video::IImage*>::iterator n;
242 n = m_images.find(name);
243 if(n != m_images.end()){
244 n->second->grab(); // Grab for caller
247 video::IVideoDriver* driver = device->getVideoDriver();
248 std::string path = getTexturePath(name.c_str());
250 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
251 <<name<<"\""<<std::endl;
254 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
256 video::IImage *img = driver->createImageFromFile(path.c_str());
257 // Even if could not be loaded, put as NULL
258 //m_images[name] = img;
260 m_images[name] = img;
261 img->grab(); // Grab for caller
266 std::map<std::string, video::IImage*> m_images;
273 class TextureSource : public IWritableTextureSource
276 TextureSource(IrrlichtDevice *device);
281 Now, assume a texture with the id 1 exists, and has the name
282 "stone.png^mineral1".
283 Then a random thread calls getTextureId for a texture called
284 "stone.png^mineral1^crack0".
285 ...Now, WTF should happen? Well:
286 - getTextureId strips off stuff recursively from the end until
287 the remaining part is found, or nothing is left when
288 something is stripped out
290 But it is slow to search for textures by names and modify them
292 - ContentFeatures is made to contain ids for the basic plain
294 - Crack textures can be slow by themselves, but the framework
298 - Assume a texture with the id 1 exists, and has the name
299 "stone.png^mineral1" and is specified as a part of some atlas.
300 - Now getNodeTile() stumbles upon a node which uses
301 texture id 1, and determines that MATERIAL_FLAG_CRACK
302 must be applied to the tile
303 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
304 has received the current crack level 0 from the client. It
305 finds out the name of the texture with getTextureName(1),
306 appends "^crack0" to it and gets a new texture id with
307 getTextureId("stone.png^mineral1^crack0").
312 Gets a texture id from cache or
313 - if main thread, from getTextureIdDirect
314 - if other thread, adds to request queue and waits for main thread
316 u32 getTextureId(const std::string &name);
322 "stone.png^mineral_coal.png"
323 "stone.png^mineral_coal.png^crack1"
325 - If texture specified by name is found from cache, return the
327 - Otherwise generate the texture, add to cache and return id.
328 Recursion is used to find out the largest found part of the
329 texture and continue based on it.
331 The id 0 points to a NULL texture. It is returned in case of error.
333 u32 getTextureIdDirect(const std::string &name);
335 // Finds out the name of a cached texture.
336 std::string getTextureName(u32 id);
339 If texture specified by the name pointed by the id doesn't
340 exist, create it, then return the cached texture.
342 Can be called from any thread. If called from some other thread
343 and not found in cache, the call is queued to the main thread
346 AtlasPointer getTexture(u32 id);
348 AtlasPointer getTexture(const std::string &name)
350 return getTexture(getTextureId(name));
353 // Gets a separate texture
354 video::ITexture* getTextureRaw(const std::string &name)
356 AtlasPointer ap = getTexture(name + "^[forcesingle");
360 // Gets a separate texture atlas pointer
361 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
363 return getTexture(getTextureName(ap.id) + "^[forcesingle");
366 // Returns a pointer to the irrlicht device
367 virtual IrrlichtDevice* getDevice()
372 // Update new texture pointer and texture coordinates to an
373 // AtlasPointer based on it's texture id
374 void updateAP(AtlasPointer &ap);
376 bool isKnownSourceImage(const std::string &name)
378 bool is_known = false;
379 bool cache_found = m_source_image_existence.get(name, &is_known);
382 // Not found in cache; find out if a local file exists
383 is_known = (getTexturePath(name) != "");
384 m_source_image_existence.set(name, is_known);
388 // Processes queued texture requests from other threads.
389 // Shall be called from the main thread.
392 // Insert an image into the cache without touching the filesystem.
393 // Shall be called from the main thread.
394 void insertSourceImage(const std::string &name, video::IImage *img);
396 // Rebuild images and textures from the current set of source images
397 // Shall be called from the main thread.
398 void rebuildImagesAndTextures();
400 // Build the main texture atlas which contains most of the
402 void buildMainAtlas(class IGameDef *gamedef);
406 // The id of the thread that is allowed to use irrlicht directly
407 threadid_t m_main_thread;
408 // The irrlicht device
409 IrrlichtDevice *m_device;
411 // Cache of source images
412 // This should be only accessed from the main thread
413 SourceImageCache m_sourcecache;
415 // Thread-safe cache of what source images are known (true = known)
416 MutexedMap<std::string, bool> m_source_image_existence;
418 // A texture id is index in this array.
419 // The first position contains a NULL texture.
420 std::vector<SourceAtlasPointer> m_atlaspointer_cache;
421 // Maps a texture name to an index in the former.
422 std::map<std::string, u32> m_name_to_id;
423 // The two former containers are behind this mutex
424 JMutex m_atlaspointer_cache_mutex;
426 // Main texture atlas. This is filled at startup and is then not touched.
427 video::IImage *m_main_atlas_image;
428 video::ITexture *m_main_atlas_texture;
430 // Queued texture fetches (to be processed by the main thread)
431 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
434 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
436 return new TextureSource(device);
439 TextureSource::TextureSource(IrrlichtDevice *device):
441 m_main_atlas_image(NULL),
442 m_main_atlas_texture(NULL)
446 m_atlaspointer_cache_mutex.Init();
448 m_main_thread = get_current_thread_id();
450 // Add a NULL AtlasPointer as the first index, named ""
451 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
452 m_name_to_id[""] = 0;
455 TextureSource::~TextureSource()
459 u32 TextureSource::getTextureId(const std::string &name)
461 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
465 See if texture already exists
467 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
468 std::map<std::string, u32>::iterator n;
469 n = m_name_to_id.find(name);
470 if(n != m_name_to_id.end())
479 if(get_current_thread_id() == m_main_thread)
481 return getTextureIdDirect(name);
485 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
487 // We're gonna ask the result to be put into here
488 ResultQueue<std::string, u32, u8, u8> result_queue;
490 // Throw a request in
491 m_get_texture_queue.add(name, 0, 0, &result_queue);
493 infostream<<"Waiting for texture from main thread, name=\""
494 <<name<<"\""<<std::endl;
498 // Wait result for a second
499 GetResult<std::string, u32, u8, u8>
500 result = result_queue.pop_front(1000);
502 // Check that at least something worked OK
503 assert(result.key == name);
507 catch(ItemNotFoundException &e)
509 infostream<<"Waiting for texture timed out."<<std::endl;
514 infostream<<"getTextureId(): Failed"<<std::endl;
519 // Overlay image on top of another image (used for cracks)
520 void overlay(video::IImage *image, video::IImage *overlay);
522 // Draw an image on top of an another one, using the alpha channel of the
524 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
525 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
528 void brighten(video::IImage *image);
529 // Parse a transform name
530 u32 parseImageTransform(const std::string& s);
531 // Apply transform to image dimension
532 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
533 // Apply transform to image data
534 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
537 Generate image based on a string like "stone.png" or "[crack0".
538 if baseimg is NULL, it is created. Otherwise stuff is made on it.
540 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
541 IrrlichtDevice *device, SourceImageCache *sourcecache);
544 Generates an image from a full string like
545 "stone.png^mineral_coal.png^[crack0".
547 This is used by buildMainAtlas().
549 video::IImage* generate_image_from_scratch(std::string name,
550 IrrlichtDevice *device, SourceImageCache *sourcecache);
553 This method generates all the textures
555 u32 TextureSource::getTextureIdDirect(const std::string &name)
557 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
559 // Empty name means texture 0
562 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
567 Calling only allowed from main thread
569 if(get_current_thread_id() != m_main_thread)
571 errorstream<<"TextureSource::getTextureIdDirect() "
572 "called not from main thread"<<std::endl;
577 See if texture already exists
580 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
582 std::map<std::string, u32>::iterator n;
583 n = m_name_to_id.find(name);
584 if(n != m_name_to_id.end())
586 /*infostream<<"getTextureIdDirect(): \""<<name
587 <<"\" found in cache"<<std::endl;*/
592 /*infostream<<"getTextureIdDirect(): \""<<name
593 <<"\" NOT found in cache. Creating it."<<std::endl;*/
599 char separator = '^';
602 This is set to the id of the base image.
603 If left 0, there is no base image and a completely new image
606 u32 base_image_id = 0;
608 // Find last meta separator in name
609 s32 last_separator_position = -1;
610 for(s32 i=name.size()-1; i>=0; i--)
612 if(name[i] == separator)
614 last_separator_position = i;
619 If separator was found, construct the base name and make the
620 base image using a recursive call
622 std::string base_image_name;
623 if(last_separator_position != -1)
625 // Construct base name
626 base_image_name = name.substr(0, last_separator_position);
627 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
628 " to get base image of \""<<name<<"\" = \""
629 <<base_image_name<<"\""<<std::endl;*/
630 base_image_id = getTextureIdDirect(base_image_name);
633 //infostream<<"base_image_id="<<base_image_id<<std::endl;
635 video::IVideoDriver* driver = m_device->getVideoDriver();
638 video::ITexture *t = NULL;
641 An image will be built from files and then converted into a texture.
643 video::IImage *baseimg = NULL;
645 // If a base image was found, copy it to baseimg
646 if(base_image_id != 0)
648 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
650 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
652 video::IImage *image = ap.atlas_img;
656 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
657 <<"cache: \""<<base_image_name<<"\""
662 core::dimension2d<u32> dim = ap.intsize;
664 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
666 core::position2d<s32> pos_to(0,0);
667 core::position2d<s32> pos_from = ap.intpos;
671 v2s32(0,0), // position in target
672 core::rect<s32>(pos_from, dim) // from
675 /*infostream<<"getTextureIdDirect(): Loaded \""
676 <<base_image_name<<"\" from image cache"
682 Parse out the last part of the name of the image and act
686 std::string last_part_of_name = name.substr(last_separator_position+1);
687 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
689 // Generate image according to part of name
690 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
692 errorstream<<"getTextureIdDirect(): "
693 "failed to generate \""<<last_part_of_name<<"\""
697 // If no resulting image, print a warning
700 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
701 " create texture \""<<name<<"\""<<std::endl;
706 // Create texture from resulting image
707 t = driver->addTexture(name.c_str(), baseimg);
711 Add texture to caches (add NULL textures too)
714 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
716 u32 id = m_atlaspointer_cache.size();
722 core::dimension2d<u32> baseimg_dim(0,0);
724 baseimg_dim = baseimg->getDimension();
725 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
726 m_atlaspointer_cache.push_back(nap);
727 m_name_to_id[name] = id;
729 /*infostream<<"getTextureIdDirect(): "
730 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
735 std::string TextureSource::getTextureName(u32 id)
737 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
739 if(id >= m_atlaspointer_cache.size())
741 errorstream<<"TextureSource::getTextureName(): id="<<id
742 <<" >= m_atlaspointer_cache.size()="
743 <<m_atlaspointer_cache.size()<<std::endl;
747 return m_atlaspointer_cache[id].name;
751 AtlasPointer TextureSource::getTexture(u32 id)
753 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
755 if(id >= m_atlaspointer_cache.size())
756 return AtlasPointer(0, NULL);
758 return m_atlaspointer_cache[id].a;
761 void TextureSource::updateAP(AtlasPointer &ap)
763 AtlasPointer ap2 = getTexture(ap.id);
767 void TextureSource::processQueue()
772 if(!m_get_texture_queue.empty())
774 GetRequest<std::string, u32, u8, u8>
775 request = m_get_texture_queue.pop();
777 /*infostream<<"TextureSource::processQueue(): "
778 <<"got texture request with "
779 <<"name=\""<<request.key<<"\""
782 GetResult<std::string, u32, u8, u8>
784 result.key = request.key;
785 result.callers = request.callers;
786 result.item = getTextureIdDirect(request.key);
788 request.dest->push_back(result);
792 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
794 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
796 assert(get_current_thread_id() == m_main_thread);
798 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
799 m_source_image_existence.set(name, true);
802 void TextureSource::rebuildImagesAndTextures()
804 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
806 /*// Oh well... just clear everything, they'll load sometime.
807 m_atlaspointer_cache.clear();
808 m_name_to_id.clear();*/
810 video::IVideoDriver* driver = m_device->getVideoDriver();
812 // Remove source images from textures to disable inheriting textures
813 // from existing textures
814 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
815 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
816 sap->atlas_img->drop();
817 sap->atlas_img = NULL;
821 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
822 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
824 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
825 // Create texture from resulting image
826 video::ITexture *t = NULL;
828 t = driver->addTexture(sap->name.c_str(), img);
829 video::ITexture *t_old = sap->a.atlas;
832 sap->a.pos = v2f(0,0);
833 sap->a.size = v2f(1,1);
835 sap->atlas_img = img;
836 sap->intpos = v2s32(0,0);
837 sap->intsize = img->getDimension();
840 driver->removeTexture(t_old);
844 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
846 assert(gamedef->tsrc() == this);
847 INodeDefManager *ndef = gamedef->ndef();
849 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
851 //return; // Disable (for testing)
853 video::IVideoDriver* driver = m_device->getVideoDriver();
856 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
858 // Create an image of the right size
859 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
860 core::dimension2d<u32> atlas_dim(2048,2048);
861 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
862 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
863 video::IImage *atlas_img =
864 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
866 if(atlas_img == NULL)
868 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
869 "image; not building texture atlas."<<std::endl;
874 Grab list of stuff to include in the texture atlas from the
875 main content features
878 std::set<std::string> sourcelist;
880 for(u16 j=0; j<MAX_CONTENT+1; j++)
882 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
884 const ContentFeatures &f = ndef->get(j);
885 for(u32 i=0; i<6; i++)
887 std::string name = f.tiledef[i].name;
888 sourcelist.insert(name);
892 infostream<<"Creating texture atlas out of textures: ";
893 for(std::set<std::string>::iterator
894 i = sourcelist.begin();
895 i != sourcelist.end(); ++i)
897 std::string name = *i;
898 infostream<<"\""<<name<<"\" ";
900 infostream<<std::endl;
902 // Padding to disallow texture bleeding
903 // (16 needed if mipmapping is used; otherwise less will work too)
905 s32 column_padding = 16;
906 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
909 First pass: generate almost everything
911 core::position2d<s32> pos_in_atlas(0,0);
913 pos_in_atlas.X = column_padding;
914 pos_in_atlas.Y = padding;
916 for(std::set<std::string>::iterator
917 i = sourcelist.begin();
918 i != sourcelist.end(); ++i)
920 std::string name = *i;
922 // Generate image by name
923 video::IImage *img2 = generate_image_from_scratch(name, m_device,
927 errorstream<<"TextureSource::buildMainAtlas(): "
928 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
932 core::dimension2d<u32> dim = img2->getDimension();
934 // Don't add to atlas if image is too large
935 core::dimension2d<u32> max_size_in_atlas(64,64);
936 if(dim.Width > max_size_in_atlas.Width
937 || dim.Height > max_size_in_atlas.Height)
939 infostream<<"TextureSource::buildMainAtlas(): Not adding "
940 <<"\""<<name<<"\" because image is large"<<std::endl;
944 // Wrap columns and stop making atlas if atlas is full
945 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
947 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
948 errorstream<<"TextureSource::buildMainAtlas(): "
949 <<"Atlas is full, not adding more textures."
953 pos_in_atlas.Y = padding;
954 pos_in_atlas.X += column_width + column_padding*2;
957 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
958 <<"\" to texture atlas"<<std::endl;*/
960 // Tile it a few times in the X direction
961 u16 xwise_tiling = column_width / dim.Width;
962 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
964 for(u32 j=0; j<xwise_tiling; j++)
966 // Copy the copy to the atlas
967 /*img2->copyToWithAlpha(atlas_img,
968 pos_in_atlas + v2s32(j*dim.Width,0),
969 core::rect<s32>(v2s32(0,0), dim),
970 video::SColor(255,255,255,255),
972 img2->copyTo(atlas_img,
973 pos_in_atlas + v2s32(j*dim.Width,0),
974 core::rect<s32>(v2s32(0,0), dim),
978 // Copy the borders a few times to disallow texture bleeding
979 for(u32 side=0; side<2; side++) // top and bottom
980 for(s32 y0=0; y0<padding; y0++)
981 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
987 dst_y = y0 + pos_in_atlas.Y + dim.Height;
988 src_y = pos_in_atlas.Y + dim.Height - 1;
992 dst_y = -y0 + pos_in_atlas.Y-1;
993 src_y = pos_in_atlas.Y;
995 s32 x = x0 + pos_in_atlas.X;
996 video::SColor c = atlas_img->getPixel(x, src_y);
997 atlas_img->setPixel(x,dst_y,c);
1000 for(u32 side=0; side<2; side++) // left and right
1001 for(s32 x0=0; x0<column_padding; x0++)
1002 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
1008 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
1009 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1013 dst_x = -x0 + pos_in_atlas.X-1;
1014 src_x = pos_in_atlas.X;
1016 s32 y = y0 + pos_in_atlas.Y;
1017 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1019 video::SColor c = atlas_img->getPixel(src_x, src_y);
1020 atlas_img->setPixel(dst_x,dst_y,c);
1026 Add texture to caches
1029 bool reuse_old_id = false;
1030 u32 id = m_atlaspointer_cache.size();
1031 // Check old id without fetching a texture
1032 std::map<std::string, u32>::iterator n;
1033 n = m_name_to_id.find(name);
1034 // If it exists, we will replace the old definition
1035 if(n != m_name_to_id.end()){
1037 reuse_old_id = true;
1038 /*infostream<<"TextureSource::buildMainAtlas(): "
1039 <<"Replacing old AtlasPointer"<<std::endl;*/
1042 // Create AtlasPointer
1043 AtlasPointer ap(id);
1044 ap.atlas = NULL; // Set on the second pass
1045 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1046 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1047 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1048 (float)dim.Width/(float)atlas_dim.Height);
1049 ap.tiled = xwise_tiling;
1051 // Create SourceAtlasPointer and add to containers
1052 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1054 m_atlaspointer_cache[id] = nap;
1056 m_atlaspointer_cache.push_back(nap);
1057 m_name_to_id[name] = id;
1059 // Increment position
1060 pos_in_atlas.Y += dim.Height + padding * 2;
1066 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1070 Second pass: set texture pointer in generated AtlasPointers
1072 for(std::set<std::string>::iterator
1073 i = sourcelist.begin();
1074 i != sourcelist.end(); ++i)
1076 std::string name = *i;
1077 if(m_name_to_id.find(name) == m_name_to_id.end())
1079 u32 id = m_name_to_id[name];
1080 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1081 m_atlaspointer_cache[id].a.atlas = t;
1085 Write image to file so that it can be inspected
1087 /*std::string atlaspath = porting::path_user
1088 + DIR_DELIM + "generated_texture_atlas.png";
1089 infostream<<"Removing and writing texture atlas for inspection to "
1090 <<atlaspath<<std::endl;
1091 fs::RecursiveDelete(atlaspath);
1092 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1095 video::IImage* generate_image_from_scratch(std::string name,
1096 IrrlichtDevice *device, SourceImageCache *sourcecache)
1098 /*infostream<<"generate_image_from_scratch(): "
1099 "\""<<name<<"\""<<std::endl;*/
1101 video::IVideoDriver* driver = device->getVideoDriver();
1108 video::IImage *baseimg = NULL;
1110 char separator = '^';
1112 // Find last meta separator in name
1113 s32 last_separator_position = name.find_last_of(separator);
1114 //if(last_separator_position == std::npos)
1115 // last_separator_position = -1;
1117 /*infostream<<"generate_image_from_scratch(): "
1118 <<"last_separator_position="<<last_separator_position
1122 If separator was found, construct the base name and make the
1123 base image using a recursive call
1125 std::string base_image_name;
1126 if(last_separator_position != -1)
1128 // Construct base name
1129 base_image_name = name.substr(0, last_separator_position);
1130 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1131 " to get base image of \""<<name<<"\" = \""
1132 <<base_image_name<<"\""<<std::endl;*/
1133 baseimg = generate_image_from_scratch(base_image_name, device,
1138 Parse out the last part of the name of the image and act
1142 std::string last_part_of_name = name.substr(last_separator_position+1);
1143 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1145 // Generate image according to part of name
1146 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1148 errorstream<<"generate_image_from_scratch(): "
1149 "failed to generate \""<<last_part_of_name<<"\""
1157 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1158 IrrlichtDevice *device, SourceImageCache *sourcecache)
1160 video::IVideoDriver* driver = device->getVideoDriver();
1163 // Stuff starting with [ are special commands
1164 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1166 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1170 if(part_of_name != ""){
1171 errorstream<<"generate_image(): Could not load image \""
1172 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1173 errorstream<<"generate_image(): Creating a dummy"
1174 <<" image for \""<<part_of_name<<"\""<<std::endl;
1177 // Just create a dummy image
1178 //core::dimension2d<u32> dim(2,2);
1179 core::dimension2d<u32> dim(1,1);
1180 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1182 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1183 image->setPixel(1,0, video::SColor(255,0,255,0));
1184 image->setPixel(0,1, video::SColor(255,0,0,255));
1185 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1186 image->setPixel(0,0, video::SColor(255,myrand()%256,
1187 myrand()%256,myrand()%256));
1188 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1189 myrand()%256,myrand()%256));
1190 image->setPixel(0,1, video::SColor(255,myrand()%256,
1191 myrand()%256,myrand()%256));
1192 image->setPixel(1,1, video::SColor(255,myrand()%256,
1193 myrand()%256,myrand()%256));*/
1196 // If base image is NULL, load as base.
1199 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1201 Copy it this way to get an alpha channel.
1202 Otherwise images with alpha cannot be blitted on
1203 images that don't have alpha in the original file.
1205 core::dimension2d<u32> dim = image->getDimension();
1206 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1207 image->copyTo(baseimg);
1210 // Else blit on base.
1213 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1214 // Size of the copied area
1215 core::dimension2d<u32> dim = image->getDimension();
1216 //core::dimension2d<u32> dim(16,16);
1217 // Position to copy the blitted to in the base image
1218 core::position2d<s32> pos_to(0,0);
1219 // Position to copy the blitted from in the blitted image
1220 core::position2d<s32> pos_from(0,0);
1222 /*image->copyToWithAlpha(baseimg, pos_to,
1223 core::rect<s32>(pos_from, dim),
1224 video::SColor(255,255,255,255),
1226 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1233 // A special texture modification
1235 /*infostream<<"generate_image(): generating special "
1236 <<"modification \""<<part_of_name<<"\""
1240 This is the simplest of all; it just adds stuff to the
1241 name so that a separate texture is created.
1243 It is used to make textures for stuff that doesn't want
1244 to implement getting the texture from a bigger texture
1247 if(part_of_name == "[forcesingle")
1249 // If base image is NULL, create a random color
1252 core::dimension2d<u32> dim(1,1);
1253 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1255 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1256 myrand()%256,myrand()%256));
1261 Adds a cracking texture
1263 else if(part_of_name.substr(0,6) == "[crack")
1267 errorstream<<"generate_image(): baseimg==NULL "
1268 <<"for part_of_name=\""<<part_of_name
1269 <<"\", cancelling."<<std::endl;
1273 // Crack image number and overlay option
1274 s32 progression = 0;
1275 bool use_overlay = false;
1276 if(part_of_name.substr(6,1) == "o")
1278 progression = stoi(part_of_name.substr(7));
1283 progression = stoi(part_of_name.substr(6));
1284 use_overlay = false;
1287 // Size of the base image
1288 core::dimension2d<u32> dim_base = baseimg->getDimension();
1293 It is an image with a number of cracking stages
1296 video::IImage *img_crack = sourcecache->getOrLoad(
1297 "crack_anylength.png", device);
1299 if(img_crack && progression >= 0)
1301 // Dimension of original image
1302 core::dimension2d<u32> dim_crack
1303 = img_crack->getDimension();
1304 // Count of crack stages
1305 s32 crack_count = dim_crack.Height / dim_crack.Width;
1306 // Limit progression
1307 if(progression > crack_count-1)
1308 progression = crack_count-1;
1309 // Dimension of a single crack stage
1310 core::dimension2d<u32> dim_crack_cropped(
1314 // Create cropped and scaled crack images
1315 video::IImage *img_crack_cropped = driver->createImage(
1316 video::ECF_A8R8G8B8, dim_crack_cropped);
1317 video::IImage *img_crack_scaled = driver->createImage(
1318 video::ECF_A8R8G8B8, dim_base);
1320 if(img_crack_cropped && img_crack_scaled)
1323 v2s32 pos_crack(0, progression*dim_crack.Width);
1324 img_crack->copyTo(img_crack_cropped,
1326 core::rect<s32>(pos_crack, dim_crack_cropped));
1327 // Scale crack image by copying
1328 img_crack_cropped->copyToScaling(img_crack_scaled);
1329 // Copy or overlay crack image
1332 overlay(baseimg, img_crack_scaled);
1336 /*img_crack_scaled->copyToWithAlpha(
1339 core::rect<s32>(v2s32(0,0), dim_base),
1340 video::SColor(255,255,255,255));*/
1341 blit_with_alpha(img_crack_scaled, baseimg,
1342 v2s32(0,0), v2s32(0,0), dim_base);
1346 if(img_crack_scaled)
1347 img_crack_scaled->drop();
1349 if(img_crack_cropped)
1350 img_crack_cropped->drop();
1356 [combine:WxH:X,Y=filename:X,Y=filename2
1357 Creates a bigger texture from an amount of smaller ones
1359 else if(part_of_name.substr(0,8) == "[combine")
1361 Strfnd sf(part_of_name);
1363 u32 w0 = stoi(sf.next("x"));
1364 u32 h0 = stoi(sf.next(":"));
1365 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1366 core::dimension2d<u32> dim(w0,h0);
1369 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1370 baseimg->fill(video::SColor(0,0,0,0));
1372 while(sf.atend() == false)
1374 u32 x = stoi(sf.next(","));
1375 u32 y = stoi(sf.next("="));
1376 std::string filename = sf.next(":");
1377 infostream<<"Adding \""<<filename
1378 <<"\" to combined ("<<x<<","<<y<<")"
1380 video::IImage *img = sourcecache->getOrLoad(filename, device);
1383 core::dimension2d<u32> dim = img->getDimension();
1384 infostream<<"Size "<<dim.Width
1385 <<"x"<<dim.Height<<std::endl;
1386 core::position2d<s32> pos_base(x, y);
1387 video::IImage *img2 =
1388 driver->createImage(video::ECF_A8R8G8B8, dim);
1391 /*img2->copyToWithAlpha(baseimg, pos_base,
1392 core::rect<s32>(v2s32(0,0), dim),
1393 video::SColor(255,255,255,255),
1395 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1400 infostream<<"img==NULL"<<std::endl;
1407 else if(part_of_name.substr(0,9) == "[brighten")
1411 errorstream<<"generate_image(): baseimg==NULL "
1412 <<"for part_of_name=\""<<part_of_name
1413 <<"\", cancelling."<<std::endl;
1421 Make image completely opaque.
1422 Used for the leaves texture when in old leaves mode, so
1423 that the transparent parts don't look completely black
1424 when simple alpha channel is used for rendering.
1426 else if(part_of_name.substr(0,8) == "[noalpha")
1430 errorstream<<"generate_image(): baseimg==NULL "
1431 <<"for part_of_name=\""<<part_of_name
1432 <<"\", cancelling."<<std::endl;
1436 core::dimension2d<u32> dim = baseimg->getDimension();
1438 // Set alpha to full
1439 for(u32 y=0; y<dim.Height; y++)
1440 for(u32 x=0; x<dim.Width; x++)
1442 video::SColor c = baseimg->getPixel(x,y);
1444 baseimg->setPixel(x,y,c);
1449 Convert one color to transparent.
1451 else if(part_of_name.substr(0,11) == "[makealpha:")
1455 errorstream<<"generate_image(): baseimg==NULL "
1456 <<"for part_of_name=\""<<part_of_name
1457 <<"\", cancelling."<<std::endl;
1461 Strfnd sf(part_of_name.substr(11));
1462 u32 r1 = stoi(sf.next(","));
1463 u32 g1 = stoi(sf.next(","));
1464 u32 b1 = stoi(sf.next(""));
1465 std::string filename = sf.next("");
1467 core::dimension2d<u32> dim = baseimg->getDimension();
1469 /*video::IImage *oldbaseimg = baseimg;
1470 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1471 oldbaseimg->copyTo(baseimg);
1472 oldbaseimg->drop();*/
1474 // Set alpha to full
1475 for(u32 y=0; y<dim.Height; y++)
1476 for(u32 x=0; x<dim.Width; x++)
1478 video::SColor c = baseimg->getPixel(x,y);
1480 u32 g = c.getGreen();
1481 u32 b = c.getBlue();
1482 if(!(r == r1 && g == g1 && b == b1))
1485 baseimg->setPixel(x,y,c);
1490 Rotates and/or flips the image.
1492 N can be a number (between 0 and 7) or a transform name.
1493 Rotations are counter-clockwise.
1495 1 R90 rotate by 90 degrees
1496 2 R180 rotate by 180 degrees
1497 3 R270 rotate by 270 degrees
1499 5 FXR90 flip X then rotate by 90 degrees
1501 7 FYR90 flip Y then rotate by 90 degrees
1503 Note: Transform names can be concatenated to produce
1504 their product (applies the first then the second).
1505 The resulting transform will be equivalent to one of the
1506 eight existing ones, though (see: dihedral group).
1508 else if(part_of_name.substr(0,10) == "[transform")
1512 errorstream<<"generate_image(): baseimg==NULL "
1513 <<"for part_of_name=\""<<part_of_name
1514 <<"\", cancelling."<<std::endl;
1518 u32 transform = parseImageTransform(part_of_name.substr(10));
1519 core::dimension2d<u32> dim = imageTransformDimension(
1520 transform, baseimg->getDimension());
1521 video::IImage *image = driver->createImage(
1522 baseimg->getColorFormat(), dim);
1524 imageTransform(transform, baseimg, image);
1529 [inventorycube{topimage{leftimage{rightimage
1530 In every subimage, replace ^ with &.
1531 Create an "inventory cube".
1532 NOTE: This should be used only on its own.
1533 Example (a grass block (not actually used in game):
1534 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1536 else if(part_of_name.substr(0,14) == "[inventorycube")
1540 errorstream<<"generate_image(): baseimg!=NULL "
1541 <<"for part_of_name=\""<<part_of_name
1542 <<"\", cancelling."<<std::endl;
1546 str_replace_char(part_of_name, '&', '^');
1547 Strfnd sf(part_of_name);
1549 std::string imagename_top = sf.next("{");
1550 std::string imagename_left = sf.next("{");
1551 std::string imagename_right = sf.next("{");
1553 // Generate images for the faces of the cube
1554 video::IImage *img_top = generate_image_from_scratch(
1555 imagename_top, device, sourcecache);
1556 video::IImage *img_left = generate_image_from_scratch(
1557 imagename_left, device, sourcecache);
1558 video::IImage *img_right = generate_image_from_scratch(
1559 imagename_right, device, sourcecache);
1560 assert(img_top && img_left && img_right);
1562 // Create textures from images
1563 video::ITexture *texture_top = driver->addTexture(
1564 (imagename_top + "__temp__").c_str(), img_top);
1565 video::ITexture *texture_left = driver->addTexture(
1566 (imagename_left + "__temp__").c_str(), img_left);
1567 video::ITexture *texture_right = driver->addTexture(
1568 (imagename_right + "__temp__").c_str(), img_right);
1569 assert(texture_top && texture_left && texture_right);
1577 Draw a cube mesh into a render target texture
1579 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1580 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1581 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1582 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1583 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1584 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1585 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1586 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1588 core::dimension2d<u32> dim(64,64);
1589 std::string rtt_texture_name = part_of_name + "_RTT";
1591 v3f camera_position(0, 1.0, -1.5);
1592 camera_position.rotateXZBy(45);
1593 v3f camera_lookat(0, 0, 0);
1594 core::CMatrix4<f32> camera_projection_matrix;
1595 // Set orthogonal projection
1596 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1597 1.65, 1.65, 0, 100);
1599 video::SColorf ambient_light(0.2,0.2,0.2);
1600 v3f light_position(10, 100, -50);
1601 video::SColorf light_color(0.5,0.5,0.5);
1602 f32 light_radius = 1000;
1604 video::ITexture *rtt = generateTextureFromMesh(
1605 cube, device, dim, rtt_texture_name,
1608 camera_projection_matrix,
1617 // Free textures of images
1618 driver->removeTexture(texture_top);
1619 driver->removeTexture(texture_left);
1620 driver->removeTexture(texture_right);
1624 baseimg = generate_image_from_scratch(
1625 imagename_top, device, sourcecache);
1629 // Create image of render target
1630 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1633 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1637 image->copyTo(baseimg);
1642 [lowpart:percent:filename
1643 Adds the lower part of a texture
1645 else if(part_of_name.substr(0,9) == "[lowpart:")
1647 Strfnd sf(part_of_name);
1649 u32 percent = stoi(sf.next(":"));
1650 std::string filename = sf.next(":");
1651 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1654 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1655 video::IImage *img = sourcecache->getOrLoad(filename, device);
1658 core::dimension2d<u32> dim = img->getDimension();
1659 core::position2d<s32> pos_base(0, 0);
1660 video::IImage *img2 =
1661 driver->createImage(video::ECF_A8R8G8B8, dim);
1664 core::position2d<s32> clippos(0, 0);
1665 clippos.Y = dim.Height * (100-percent) / 100;
1666 core::dimension2d<u32> clipdim = dim;
1667 clipdim.Height = clipdim.Height * percent / 100 + 1;
1668 core::rect<s32> cliprect(clippos, clipdim);
1669 img2->copyToWithAlpha(baseimg, pos_base,
1670 core::rect<s32>(v2s32(0,0), dim),
1671 video::SColor(255,255,255,255),
1678 Crops a frame of a vertical animation.
1679 N = frame count, I = frame index
1681 else if(part_of_name.substr(0,15) == "[verticalframe:")
1683 Strfnd sf(part_of_name);
1685 u32 frame_count = stoi(sf.next(":"));
1686 u32 frame_index = stoi(sf.next(":"));
1688 if(baseimg == NULL){
1689 errorstream<<"generate_image(): baseimg!=NULL "
1690 <<"for part_of_name=\""<<part_of_name
1691 <<"\", cancelling."<<std::endl;
1695 v2u32 frame_size = baseimg->getDimension();
1696 frame_size.Y /= frame_count;
1698 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1701 errorstream<<"generate_image(): Could not create image "
1702 <<"for part_of_name=\""<<part_of_name
1703 <<"\", cancelling."<<std::endl;
1707 // Fill target image with transparency
1708 img->fill(video::SColor(0,0,0,0));
1710 core::dimension2d<u32> dim = frame_size;
1711 core::position2d<s32> pos_dst(0, 0);
1712 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1713 baseimg->copyToWithAlpha(img, pos_dst,
1714 core::rect<s32>(pos_src, dim),
1715 video::SColor(255,255,255,255),
1723 errorstream<<"generate_image(): Invalid "
1724 " modification: \""<<part_of_name<<"\""<<std::endl;
1731 void overlay(video::IImage *image, video::IImage *overlay)
1734 Copy overlay to image, taking alpha into account.
1735 Where image is transparent, don't copy from overlay.
1736 Images sizes must be identical.
1738 if(image == NULL || overlay == NULL)
1741 core::dimension2d<u32> dim = image->getDimension();
1742 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1743 assert(dim == dim_overlay);
1745 for(u32 y=0; y<dim.Height; y++)
1746 for(u32 x=0; x<dim.Width; x++)
1748 video::SColor c1 = image->getPixel(x,y);
1749 video::SColor c2 = overlay->getPixel(x,y);
1750 u32 a1 = c1.getAlpha();
1751 u32 a2 = c2.getAlpha();
1752 if(a1 == 255 && a2 != 0)
1754 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1755 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1756 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1758 image->setPixel(x,y,c1);
1763 Draw an image on top of an another one, using the alpha channel of the
1766 This exists because IImage::copyToWithAlpha() doesn't seem to always
1769 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1770 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1772 for(u32 y0=0; y0<size.Y; y0++)
1773 for(u32 x0=0; x0<size.X; x0++)
1775 s32 src_x = src_pos.X + x0;
1776 s32 src_y = src_pos.Y + y0;
1777 s32 dst_x = dst_pos.X + x0;
1778 s32 dst_y = dst_pos.Y + y0;
1779 video::SColor src_c = src->getPixel(src_x, src_y);
1780 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1781 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1782 dst->setPixel(dst_x, dst_y, dst_c);
1786 void brighten(video::IImage *image)
1791 core::dimension2d<u32> dim = image->getDimension();
1793 for(u32 y=0; y<dim.Height; y++)
1794 for(u32 x=0; x<dim.Width; x++)
1796 video::SColor c = image->getPixel(x,y);
1797 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1798 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1799 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1800 image->setPixel(x,y,c);
1804 u32 parseImageTransform(const std::string& s)
1806 int total_transform = 0;
1808 std::string transform_names[8];
1809 transform_names[0] = "i";
1810 transform_names[1] = "r90";
1811 transform_names[2] = "r180";
1812 transform_names[3] = "r270";
1813 transform_names[4] = "fx";
1814 transform_names[6] = "fy";
1816 std::size_t pos = 0;
1817 while(pos < s.size())
1820 for(int i = 0; i <= 7; ++i)
1822 const std::string &name_i = transform_names[i];
1824 if(s[pos] == ('0' + i))
1830 else if(!(name_i.empty()) &&
1831 lowercase(s.substr(pos, name_i.size())) == name_i)
1834 pos += name_i.size();
1841 // Multiply total_transform and transform in the group D4
1844 new_total = (transform + total_transform) % 4;
1846 new_total = (transform - total_transform + 8) % 4;
1847 if((transform >= 4) ^ (total_transform >= 4))
1850 total_transform = new_total;
1852 return total_transform;
1855 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1857 if(transform % 2 == 0)
1860 return core::dimension2d<u32>(dim.Height, dim.Width);
1863 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1865 if(src == NULL || dst == NULL)
1868 core::dimension2d<u32> srcdim = src->getDimension();
1869 core::dimension2d<u32> dstdim = dst->getDimension();
1871 assert(dstdim == imageTransformDimension(transform, srcdim));
1872 assert(transform >= 0 && transform <= 7);
1875 Compute the transformation from source coordinates (sx,sy)
1876 to destination coordinates (dx,dy).
1880 if(transform == 0) // identity
1881 sxn = 0, syn = 2; // sx = dx, sy = dy
1882 else if(transform == 1) // rotate by 90 degrees ccw
1883 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1884 else if(transform == 2) // rotate by 180 degrees
1885 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1886 else if(transform == 3) // rotate by 270 degrees ccw
1887 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1888 else if(transform == 4) // flip x
1889 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1890 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1891 sxn = 2, syn = 0; // sx = dy, sy = dx
1892 else if(transform == 6) // flip y
1893 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1894 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1895 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1897 for(u32 dy=0; dy<dstdim.Height; dy++)
1898 for(u32 dx=0; dx<dstdim.Width; dx++)
1900 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1901 u32 sx = entries[sxn];
1902 u32 sy = entries[syn];
1903 video::SColor c = src->getPixel(sx,sy);
1904 dst->setPixel(dx,dy,c);