Line data Source code
1 : /*
2 : Minetest
3 : Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 :
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.
9 :
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.
14 :
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.
18 : */
19 :
20 : #include "tile.h"
21 :
22 : #include <ICameraSceneNode.h>
23 : #include "util/string.h"
24 : #include "util/container.h"
25 : #include "util/thread.h"
26 : #include "util/numeric.h"
27 : #include "irrlichttypes_extrabloated.h"
28 : #include "debug.h"
29 : #include "filesys.h"
30 : #include "settings.h"
31 : #include "mesh.h"
32 : #include "log.h"
33 : #include "gamedef.h"
34 : #include "strfnd.h"
35 : #include "util/string.h" // for parseColorString()
36 : #include "imagefilters.h"
37 : #include "guiscalingfilter.h"
38 :
39 : #ifdef __ANDROID__
40 : #include <GLES/gl.h>
41 : #endif
42 :
43 : /*
44 : A cache from texture name to texture path
45 : */
46 1 : MutexedMap<std::string, std::string> g_texturename_to_path_cache;
47 :
48 : /*
49 : Replaces the filename extension.
50 : eg:
51 : std::string image = "a/image.png"
52 : replace_ext(image, "jpg")
53 : -> image = "a/image.jpg"
54 : Returns true on success.
55 : */
56 64839 : static bool replace_ext(std::string &path, const char *ext)
57 : {
58 64839 : if (ext == NULL)
59 6460 : return false;
60 : // Find place of last dot, fail if \ or / found.
61 58379 : s32 last_dot_i = -1;
62 233516 : for (s32 i=path.size()-1; i>=0; i--)
63 : {
64 233516 : if (path[i] == '.')
65 : {
66 58379 : last_dot_i = i;
67 58379 : break;
68 : }
69 :
70 175137 : if (path[i] == '\\' || path[i] == '/')
71 0 : break;
72 : }
73 : // If not found, return an empty string
74 58379 : if (last_dot_i == -1)
75 0 : return false;
76 : // Else make the new path
77 58379 : path = path.substr(0, last_dot_i+1) + ext;
78 58379 : return true;
79 : }
80 :
81 : /*
82 : Find out the full path of an image by trying different filename
83 : extensions.
84 :
85 : If failed, return "".
86 : */
87 6699 : std::string getImagePath(std::string path)
88 : {
89 : // A NULL-ended list of possible image extensions
90 : const char *extensions[] = {
91 : "png", "jpg", "bmp", "tga",
92 : "pcx", "ppm", "psd", "wal", "rgb",
93 : NULL
94 6699 : };
95 : // If there is no extension, add one
96 6699 : if (removeStringEnd(path, extensions) == "")
97 2 : path = path + ".png";
98 : // Check paths until something is found to exist
99 6699 : const char **ext = extensions;
100 58140 : do{
101 64839 : bool r = replace_ext(path, *ext);
102 64839 : if (r == false)
103 6460 : return "";
104 58379 : if (fs::PathExists(path))
105 239 : return path;
106 : }
107 : while((++ext) != NULL);
108 :
109 0 : return "";
110 : }
111 :
112 : /*
113 : Gets the path to a texture by first checking if the texture exists
114 : in texture_path and if not, using the data path.
115 :
116 : Checks all supported extensions by replacing the original extension.
117 :
118 : If not found, returns "".
119 :
120 : Utilizes a thread-safe cache.
121 : */
122 3463 : std::string getTexturePath(const std::string &filename)
123 : {
124 3463 : std::string fullpath = "";
125 : /*
126 : Check from cache
127 : */
128 3463 : bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
129 3463 : if (incache)
130 1 : return fullpath;
131 :
132 : /*
133 : Check from texture_path
134 : */
135 6924 : std::string texture_path = g_settings->get("texture_path");
136 3462 : if (texture_path != "")
137 : {
138 6924 : std::string testpath = texture_path + DIR_DELIM + filename;
139 : // Check all filename extensions. Returns "" if not found.
140 3462 : fullpath = getImagePath(testpath);
141 : }
142 :
143 : /*
144 : Check from default data directory
145 : */
146 3462 : if (fullpath == "")
147 : {
148 9702 : std::string base_path = porting::path_share + DIR_DELIM + "textures"
149 9702 : + DIR_DELIM + "base" + DIR_DELIM + "pack";
150 6468 : std::string testpath = base_path + DIR_DELIM + filename;
151 : // Check all filename extensions. Returns "" if not found.
152 3234 : fullpath = getImagePath(testpath);
153 : }
154 :
155 : // Add to cache (also an empty result is cached)
156 3462 : g_texturename_to_path_cache.set(filename, fullpath);
157 :
158 : // Finally return it
159 3462 : return fullpath;
160 : }
161 :
162 1 : void clearTextureNameCache()
163 : {
164 1 : g_texturename_to_path_cache.clear();
165 1 : }
166 :
167 : /*
168 : Stores internal information about a texture.
169 : */
170 :
171 16221 : struct TextureInfo
172 : {
173 : std::string name;
174 : video::ITexture *texture;
175 :
176 2677 : TextureInfo(
177 : const std::string &name_,
178 : video::ITexture *texture_=NULL
179 : ):
180 : name(name_),
181 2677 : texture(texture_)
182 : {
183 2677 : }
184 : };
185 :
186 : /*
187 : SourceImageCache: A cache used for storing source images.
188 : */
189 :
190 1 : class SourceImageCache
191 : {
192 : public:
193 2 : ~SourceImageCache() {
194 6879 : for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
195 4586 : iter != m_images.end(); iter++) {
196 2292 : iter->second->drop();
197 : }
198 1 : m_images.clear();
199 1 : }
200 2283 : void insert(const std::string &name, video::IImage *img,
201 : bool prefer_local, video::IVideoDriver *driver)
202 : {
203 : assert(img); // Pre-condition
204 : // Remove old image
205 2283 : std::map<std::string, video::IImage*>::iterator n;
206 2283 : n = m_images.find(name);
207 2283 : if (n != m_images.end()){
208 0 : if (n->second)
209 0 : n->second->drop();
210 : }
211 :
212 2283 : video::IImage* toadd = img;
213 2283 : bool need_to_grab = true;
214 :
215 : // Try to use local texture instead if asked to
216 2283 : if (prefer_local){
217 4566 : std::string path = getTexturePath(name);
218 2283 : if (path != ""){
219 227 : video::IImage *img2 = driver->createImageFromFile(path.c_str());
220 227 : if (img2){
221 227 : toadd = img2;
222 227 : need_to_grab = false;
223 : }
224 : }
225 : }
226 :
227 2283 : if (need_to_grab)
228 2056 : toadd->grab();
229 2283 : m_images[name] = toadd;
230 2283 : }
231 : video::IImage* get(const std::string &name)
232 : {
233 : std::map<std::string, video::IImage*>::iterator n;
234 : n = m_images.find(name);
235 : if (n != m_images.end())
236 : return n->second;
237 : return NULL;
238 : }
239 : // Primarily fetches from cache, secondarily tries to read from filesystem
240 3755 : video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
241 : {
242 3755 : std::map<std::string, video::IImage*>::iterator n;
243 3755 : n = m_images.find(name);
244 3755 : if (n != m_images.end()){
245 3744 : n->second->grab(); // Grab for caller
246 3744 : return n->second;
247 : }
248 11 : video::IVideoDriver* driver = device->getVideoDriver();
249 22 : std::string path = getTexturePath(name);
250 11 : if (path == ""){
251 2 : infostream<<"SourceImageCache::getOrLoad(): No path found for \""
252 2 : <<name<<"\""<<std::endl;
253 2 : return NULL;
254 : }
255 9 : infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
256 9 : <<"\""<<std::endl;
257 9 : video::IImage *img = driver->createImageFromFile(path.c_str());
258 :
259 9 : if (img){
260 9 : m_images[name] = img;
261 9 : img->grab(); // Grab for caller
262 : }
263 9 : return img;
264 : }
265 : private:
266 : std::map<std::string, video::IImage*> m_images;
267 : };
268 :
269 : /*
270 : TextureSource
271 : */
272 :
273 : class TextureSource : public IWritableTextureSource
274 : {
275 : public:
276 : TextureSource(IrrlichtDevice *device);
277 : virtual ~TextureSource();
278 :
279 : /*
280 : Example case:
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
289 :
290 : But it is slow to search for textures by names and modify them
291 : like that?
292 : - ContentFeatures is made to contain ids for the basic plain
293 : textures
294 : - Crack textures can be slow by themselves, but the framework
295 : must be fast.
296 :
297 : Example case #2:
298 : - Assume a texture with the id 1 exists, and has the name
299 : "stone.png^mineral_coal.png".
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^mineral_coal.png^crack0").
308 :
309 : */
310 :
311 : /*
312 : Gets a texture id from cache or
313 : - if main thread, generates the texture, adds to cache and returns id.
314 : - if other thread, adds to request queue and waits for main thread.
315 :
316 : The id 0 points to a NULL texture. It is returned in case of error.
317 : */
318 : u32 getTextureId(const std::string &name);
319 :
320 : // Finds out the name of a cached texture.
321 : std::string getTextureName(u32 id);
322 :
323 : /*
324 : If texture specified by the name pointed by the id doesn't
325 : exist, create it, then return the cached texture.
326 :
327 : Can be called from any thread. If called from some other thread
328 : and not found in cache, the call is queued to the main thread
329 : for processing.
330 : */
331 : video::ITexture* getTexture(u32 id);
332 :
333 : video::ITexture* getTexture(const std::string &name, u32 *id);
334 :
335 : /*
336 : Get a texture specifically intended for mesh
337 : application, i.e. not HUD, compositing, or other 2D
338 : use. This texture may be a different size and may
339 : have had additional filters applied.
340 : */
341 : video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
342 :
343 : // Returns a pointer to the irrlicht device
344 0 : virtual IrrlichtDevice* getDevice()
345 : {
346 0 : return m_device;
347 : }
348 :
349 124688 : bool isKnownSourceImage(const std::string &name)
350 : {
351 124688 : bool is_known = false;
352 124688 : bool cache_found = m_source_image_existence.get(name, &is_known);
353 124688 : if (cache_found)
354 123519 : return is_known;
355 : // Not found in cache; find out if a local file exists
356 1169 : is_known = (getTexturePath(name) != "");
357 1169 : m_source_image_existence.set(name, is_known);
358 1169 : return is_known;
359 : }
360 :
361 : // Processes queued texture requests from other threads.
362 : // Shall be called from the main thread.
363 : void processQueue();
364 :
365 : // Insert an image into the cache without touching the filesystem.
366 : // Shall be called from the main thread.
367 : void insertSourceImage(const std::string &name, video::IImage *img);
368 :
369 : // Rebuild images and textures from the current set of source images
370 : // Shall be called from the main thread.
371 : void rebuildImagesAndTextures();
372 :
373 : // Render a mesh to a texture.
374 : // Returns NULL if render-to-texture failed.
375 : // Shall be called from the main thread.
376 : video::ITexture* generateTextureFromMesh(
377 : const TextureFromMeshParams ¶ms);
378 :
379 : // Generates an image from a full string like
380 : // "stone.png^mineral_coal.png^[crack:1:0".
381 : // Shall be called from the main thread.
382 : video::IImage* generateImage(const std::string &name);
383 :
384 : video::ITexture* getNormalTexture(const std::string &name);
385 : video::SColor getTextureAverageColor(const std::string &name);
386 :
387 : private:
388 :
389 : // The id of the thread that is allowed to use irrlicht directly
390 : threadid_t m_main_thread;
391 : // The irrlicht device
392 : IrrlichtDevice *m_device;
393 :
394 : // Cache of source images
395 : // This should be only accessed from the main thread
396 : SourceImageCache m_sourcecache;
397 :
398 : // Generate a texture
399 : u32 generateTexture(const std::string &name);
400 :
401 : // Generate image based on a string like "stone.png" or "[crack:1:0".
402 : // if baseimg is NULL, it is created. Otherwise stuff is made on it.
403 : bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
404 :
405 : // Thread-safe cache of what source images are known (true = known)
406 : MutexedMap<std::string, bool> m_source_image_existence;
407 :
408 : // A texture id is index in this array.
409 : // The first position contains a NULL texture.
410 : std::vector<TextureInfo> m_textureinfo_cache;
411 : // Maps a texture name to an index in the former.
412 : std::map<std::string, u32> m_name_to_id;
413 : // The two former containers are behind this mutex
414 : JMutex m_textureinfo_cache_mutex;
415 :
416 : // Queued texture fetches (to be processed by the main thread)
417 : RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
418 :
419 : // Textures that have been overwritten with other ones
420 : // but can't be deleted because the ITexture* might still be used
421 : std::vector<video::ITexture*> m_texture_trash;
422 :
423 : // Cached settings needed for making textures from meshes
424 : bool m_setting_trilinear_filter;
425 : bool m_setting_bilinear_filter;
426 : bool m_setting_anisotropic_filter;
427 : };
428 :
429 1 : IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
430 : {
431 1 : return new TextureSource(device);
432 : }
433 :
434 1 : TextureSource::TextureSource(IrrlichtDevice *device):
435 1 : m_device(device)
436 : {
437 : assert(m_device); // Pre-condition
438 :
439 1 : m_main_thread = get_current_thread_id();
440 :
441 : // Add a NULL TextureInfo as the first index, named ""
442 1 : m_textureinfo_cache.push_back(TextureInfo(""));
443 1 : m_name_to_id[""] = 0;
444 :
445 : // Cache some settings
446 : // Note: Since this is only done once, the game must be restarted
447 : // for these settings to take effect
448 1 : m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
449 1 : m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
450 1 : m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
451 1 : }
452 :
453 3 : TextureSource::~TextureSource()
454 : {
455 1 : video::IVideoDriver* driver = m_device->getVideoDriver();
456 :
457 1 : unsigned int textures_before = driver->getTextureCount();
458 :
459 8033 : for (std::vector<TextureInfo>::iterator iter =
460 1 : m_textureinfo_cache.begin();
461 5356 : iter != m_textureinfo_cache.end(); iter++)
462 : {
463 : //cleanup texture
464 2677 : if (iter->texture)
465 2676 : driver->removeTexture(iter->texture);
466 : }
467 1 : m_textureinfo_cache.clear();
468 :
469 20 : for (std::vector<video::ITexture*>::iterator iter =
470 8 : m_texture_trash.begin(); iter != m_texture_trash.end();
471 : iter++) {
472 6 : video::ITexture *t = *iter;
473 :
474 : //cleanup trashed texture
475 6 : driver->removeTexture(t);
476 : }
477 :
478 1 : infostream << "~TextureSource() "<< textures_before << "/"
479 2 : << driver->getTextureCount() << std::endl;
480 2 : }
481 :
482 116172 : u32 TextureSource::getTextureId(const std::string &name)
483 : {
484 : //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
485 :
486 : {
487 : /*
488 : See if texture already exists
489 : */
490 118848 : JMutexAutoLock lock(m_textureinfo_cache_mutex);
491 116173 : std::map<std::string, u32>::iterator n;
492 116173 : n = m_name_to_id.find(name);
493 116173 : if (n != m_name_to_id.end())
494 : {
495 113497 : return n->second;
496 : }
497 : }
498 :
499 : /*
500 : Get texture
501 : */
502 2676 : if (get_current_thread_id() == m_main_thread)
503 : {
504 2674 : return generateTexture(name);
505 : }
506 : else
507 : {
508 2 : infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
509 :
510 : // We're gonna ask the result to be put into here
511 2 : static ResultQueue<std::string, u32, u8, u8> result_queue;
512 :
513 : // Throw a request in
514 2 : m_get_texture_queue.add(name, 0, 0, &result_queue);
515 :
516 : /*infostream<<"Waiting for texture from main thread, name=\""
517 : <<name<<"\""<<std::endl;*/
518 :
519 : try
520 : {
521 0 : while(true) {
522 : // Wait result for a second
523 : GetResult<std::string, u32, u8, u8>
524 2 : result = result_queue.pop_front(1000);
525 :
526 2 : if (result.key == name) {
527 2 : return result.item;
528 : }
529 : }
530 : }
531 0 : catch(ItemNotFoundException &e)
532 : {
533 0 : errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
534 0 : return 0;
535 : }
536 : }
537 :
538 : infostream<<"getTextureId(): Failed"<<std::endl;
539 :
540 : return 0;
541 : }
542 :
543 : // Draw an image on top of an another one, using the alpha channel of the
544 : // source image
545 : static void blit_with_alpha(video::IImage *src, video::IImage *dst,
546 : v2s32 src_pos, v2s32 dst_pos, v2u32 size);
547 :
548 : // Like blit_with_alpha, but only modifies destination pixels that
549 : // are fully opaque
550 : static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
551 : v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552 :
553 : // Like blit_with_alpha overlay, but uses an int to calculate the ratio
554 : // and modifies any destination pixels that are not fully transparent
555 : static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
556 : v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
557 :
558 : // Apply a mask to an image
559 : static void apply_mask(video::IImage *mask, video::IImage *dst,
560 : v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
561 :
562 : // Draw or overlay a crack
563 : static void draw_crack(video::IImage *crack, video::IImage *dst,
564 : bool use_overlay, s32 frame_count, s32 progression,
565 : video::IVideoDriver *driver);
566 :
567 : // Brighten image
568 : void brighten(video::IImage *image);
569 : // Parse a transform name
570 : u32 parseImageTransform(const std::string& s);
571 : // Apply transform to image dimension
572 : core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
573 : // Apply transform to image data
574 : void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
575 :
576 : /*
577 : This method generates all the textures
578 : */
579 2676 : u32 TextureSource::generateTexture(const std::string &name)
580 : {
581 : //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
582 :
583 : // Empty name means texture 0
584 2676 : if (name == "") {
585 0 : infostream<<"generateTexture(): name is empty"<<std::endl;
586 0 : return 0;
587 : }
588 :
589 : {
590 : /*
591 : See if texture already exists
592 : */
593 5352 : JMutexAutoLock lock(m_textureinfo_cache_mutex);
594 2676 : std::map<std::string, u32>::iterator n;
595 2676 : n = m_name_to_id.find(name);
596 2676 : if (n != m_name_to_id.end()) {
597 0 : return n->second;
598 : }
599 : }
600 :
601 : /*
602 : Calling only allowed from main thread
603 : */
604 2676 : if (get_current_thread_id() != m_main_thread) {
605 : errorstream<<"TextureSource::generateTexture() "
606 0 : "called not from main thread"<<std::endl;
607 0 : return 0;
608 : }
609 :
610 2676 : video::IVideoDriver *driver = m_device->getVideoDriver();
611 2676 : sanity_check(driver);
612 :
613 2676 : video::IImage *img = generateImage(name);
614 :
615 2676 : video::ITexture *tex = NULL;
616 :
617 2676 : if (img != NULL) {
618 : #ifdef __ANDROID__
619 : img = Align2Npot2(img, driver);
620 : #endif
621 : // Create texture from resulting image
622 2676 : tex = driver->addTexture(name.c_str(), img);
623 2676 : guiScalingCache(io::path(name.c_str()), driver, img);
624 2676 : img->drop();
625 : }
626 :
627 : /*
628 : Add texture to caches (add NULL textures too)
629 : */
630 :
631 5352 : JMutexAutoLock lock(m_textureinfo_cache_mutex);
632 :
633 2676 : u32 id = m_textureinfo_cache.size();
634 5352 : TextureInfo ti(name, tex);
635 2676 : m_textureinfo_cache.push_back(ti);
636 2676 : m_name_to_id[name] = id;
637 :
638 2676 : return id;
639 : }
640 :
641 0 : std::string TextureSource::getTextureName(u32 id)
642 : {
643 0 : JMutexAutoLock lock(m_textureinfo_cache_mutex);
644 :
645 0 : if (id >= m_textureinfo_cache.size())
646 : {
647 0 : errorstream<<"TextureSource::getTextureName(): id="<<id
648 0 : <<" >= m_textureinfo_cache.size()="
649 0 : <<m_textureinfo_cache.size()<<std::endl;
650 0 : return "";
651 : }
652 :
653 0 : return m_textureinfo_cache[id].name;
654 : }
655 :
656 1063916 : video::ITexture* TextureSource::getTexture(u32 id)
657 : {
658 2127833 : JMutexAutoLock lock(m_textureinfo_cache_mutex);
659 :
660 1063917 : if (id >= m_textureinfo_cache.size())
661 0 : return NULL;
662 :
663 1063917 : return m_textureinfo_cache[id].texture;
664 : }
665 :
666 116172 : video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
667 : {
668 116172 : u32 actual_id = getTextureId(name);
669 116173 : if (id){
670 68336 : *id = actual_id;
671 : }
672 116173 : return getTexture(actual_id);
673 : }
674 :
675 107505 : video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
676 : {
677 107505 : return getTexture(name + "^[applyfiltersformesh", id);
678 : }
679 :
680 1168 : void TextureSource::processQueue()
681 : {
682 : /*
683 : Fetch textures
684 : */
685 : //NOTE this is only thread safe for ONE consumer thread!
686 1168 : if (!m_get_texture_queue.empty())
687 : {
688 : GetRequest<std::string, u32, u8, u8>
689 4 : request = m_get_texture_queue.pop();
690 :
691 : /*infostream<<"TextureSource::processQueue(): "
692 : <<"got texture request with "
693 : <<"name=\""<<request.key<<"\""
694 : <<std::endl;*/
695 :
696 2 : m_get_texture_queue.pushResult(request, generateTexture(request.key));
697 : }
698 1168 : }
699 :
700 2283 : void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
701 : {
702 : //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
703 :
704 2283 : sanity_check(get_current_thread_id() == m_main_thread);
705 :
706 2283 : m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
707 2283 : m_source_image_existence.set(name, true);
708 2283 : }
709 :
710 1 : void TextureSource::rebuildImagesAndTextures()
711 : {
712 2 : JMutexAutoLock lock(m_textureinfo_cache_mutex);
713 :
714 1 : video::IVideoDriver* driver = m_device->getVideoDriver();
715 1 : sanity_check(driver);
716 :
717 : // Recreate textures
718 7 : for (u32 i=0; i<m_textureinfo_cache.size(); i++){
719 6 : TextureInfo *ti = &m_textureinfo_cache[i];
720 6 : video::IImage *img = generateImage(ti->name);
721 : #ifdef __ANDROID__
722 : img = Align2Npot2(img, driver);
723 : sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
724 : sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
725 : #endif
726 : // Create texture from resulting image
727 6 : video::ITexture *t = NULL;
728 6 : if (img) {
729 6 : t = driver->addTexture(ti->name.c_str(), img);
730 6 : guiScalingCache(io::path(ti->name.c_str()), driver, img);
731 6 : img->drop();
732 : }
733 6 : video::ITexture *t_old = ti->texture;
734 : // Replace texture
735 6 : ti->texture = t;
736 :
737 6 : if (t_old)
738 5 : m_texture_trash.push_back(t_old);
739 : }
740 1 : }
741 :
742 1 : video::ITexture* TextureSource::generateTextureFromMesh(
743 : const TextureFromMeshParams ¶ms)
744 : {
745 1 : video::IVideoDriver *driver = m_device->getVideoDriver();
746 1 : sanity_check(driver);
747 :
748 : #ifdef __ANDROID__
749 : const GLubyte* renderstr = glGetString(GL_RENDERER);
750 : std::string renderer((char*) renderstr);
751 :
752 : // use no render to texture hack
753 : if (
754 : (renderer.find("Adreno") != std::string::npos) ||
755 : (renderer.find("Mali") != std::string::npos) ||
756 : (renderer.find("Immersion") != std::string::npos) ||
757 : (renderer.find("Tegra") != std::string::npos) ||
758 : g_settings->getBool("inventory_image_hack")
759 : ) {
760 : // Get a scene manager
761 : scene::ISceneManager *smgr_main = m_device->getSceneManager();
762 : sanity_check(smgr_main);
763 : scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
764 : sanity_check(smgr);
765 :
766 : const float scaling = 0.2;
767 :
768 : scene::IMeshSceneNode* meshnode =
769 : smgr->addMeshSceneNode(params.mesh, NULL,
770 : -1, v3f(0,0,0), v3f(0,0,0),
771 : v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
772 : meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
773 : meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
774 : meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
775 : meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
776 : meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
777 :
778 : scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
779 : params.camera_position, params.camera_lookat);
780 : // second parameter of setProjectionMatrix (isOrthogonal) is ignored
781 : camera->setProjectionMatrix(params.camera_projection_matrix, false);
782 :
783 : smgr->setAmbientLight(params.ambient_light);
784 : smgr->addLightSceneNode(0,
785 : params.light_position,
786 : params.light_color,
787 : params.light_radius*scaling);
788 :
789 : core::dimension2d<u32> screen = driver->getScreenSize();
790 :
791 : // Render scene
792 : driver->beginScene(true, true, video::SColor(0,0,0,0));
793 : driver->clearZBuffer();
794 : smgr->drawAll();
795 :
796 : core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
797 :
798 : irr::video::IImage* rawImage =
799 : driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
800 :
801 : u8* pixels = static_cast<u8*>(rawImage->lock());
802 : if (!pixels)
803 : {
804 : rawImage->drop();
805 : return NULL;
806 : }
807 :
808 : core::rect<s32> source(
809 : screen.Width /2 - (screen.Width * (scaling / 2)),
810 : screen.Height/2 - (screen.Height * (scaling / 2)),
811 : screen.Width /2 + (screen.Width * (scaling / 2)),
812 : screen.Height/2 + (screen.Height * (scaling / 2))
813 : );
814 :
815 : glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
816 : partsize.Width, partsize.Height, GL_RGBA,
817 : GL_UNSIGNED_BYTE, pixels);
818 :
819 : driver->endScene();
820 :
821 : // Drop scene manager
822 : smgr->drop();
823 :
824 : unsigned int pixelcount = partsize.Width*partsize.Height;
825 :
826 : u8* runptr = pixels;
827 : for (unsigned int i=0; i < pixelcount; i++) {
828 :
829 : u8 B = *runptr;
830 : u8 G = *(runptr+1);
831 : u8 R = *(runptr+2);
832 : u8 A = *(runptr+3);
833 :
834 : //BGRA -> RGBA
835 : *runptr = R;
836 : runptr ++;
837 : *runptr = G;
838 : runptr ++;
839 : *runptr = B;
840 : runptr ++;
841 : *runptr = A;
842 : runptr ++;
843 : }
844 :
845 : video::IImage* inventory_image =
846 : driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
847 :
848 : rawImage->copyToScaling(inventory_image);
849 : rawImage->drop();
850 :
851 : guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
852 :
853 : video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
854 : inventory_image->drop();
855 :
856 : if (rtt == NULL) {
857 : errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
858 : return NULL;
859 : }
860 :
861 : driver->makeColorKeyTexture(rtt, v2s32(0,0));
862 :
863 : if (params.delete_texture_on_shutdown)
864 : m_texture_trash.push_back(rtt);
865 :
866 : return rtt;
867 : }
868 : #endif
869 :
870 1 : if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
871 : {
872 : static bool warned = false;
873 0 : if (!warned)
874 : {
875 0 : errorstream<<"TextureSource::generateTextureFromMesh(): "
876 0 : <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
877 0 : warned = true;
878 : }
879 0 : return NULL;
880 : }
881 :
882 : // Create render target texture
883 2 : video::ITexture *rtt = driver->addRenderTargetTexture(
884 : params.dim, params.rtt_texture_name.c_str(),
885 2 : video::ECF_A8R8G8B8);
886 1 : if (rtt == NULL)
887 : {
888 0 : errorstream<<"TextureSource::generateTextureFromMesh(): "
889 0 : <<"addRenderTargetTexture returned NULL."<<std::endl;
890 0 : return NULL;
891 : }
892 :
893 : // Set render target
894 1 : if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
895 0 : driver->removeTexture(rtt);
896 0 : errorstream<<"TextureSource::generateTextureFromMesh(): "
897 0 : <<"failed to set render target"<<std::endl;
898 0 : return NULL;
899 : }
900 :
901 : // Get a scene manager
902 1 : scene::ISceneManager *smgr_main = m_device->getSceneManager();
903 : assert(smgr_main);
904 1 : scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
905 : assert(smgr);
906 :
907 : scene::IMeshSceneNode* meshnode =
908 2 : smgr->addMeshSceneNode(params.mesh, NULL,
909 2 : -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
910 1 : meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
911 1 : meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
912 1 : meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
913 1 : meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
914 1 : meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
915 :
916 1 : scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
917 2 : params.camera_position, params.camera_lookat);
918 : // second parameter of setProjectionMatrix (isOrthogonal) is ignored
919 1 : camera->setProjectionMatrix(params.camera_projection_matrix, false);
920 :
921 1 : smgr->setAmbientLight(params.ambient_light);
922 1 : smgr->addLightSceneNode(0,
923 : params.light_position,
924 : params.light_color,
925 2 : params.light_radius);
926 :
927 : // Render scene
928 1 : driver->beginScene(true, true, video::SColor(0,0,0,0));
929 1 : smgr->drawAll();
930 1 : driver->endScene();
931 :
932 : // Drop scene manager
933 1 : smgr->drop();
934 :
935 : // Unset render target
936 1 : driver->setRenderTarget(0, false, true, 0);
937 :
938 1 : if (params.delete_texture_on_shutdown)
939 1 : m_texture_trash.push_back(rtt);
940 :
941 1 : return rtt;
942 : }
943 :
944 5561 : video::IImage* TextureSource::generateImage(const std::string &name)
945 : {
946 : /*
947 : Get the base image
948 : */
949 :
950 5561 : const char separator = '^';
951 5561 : const char paren_open = '(';
952 5561 : const char paren_close = ')';
953 :
954 : // Find last separator in the name
955 5561 : s32 last_separator_pos = -1;
956 5561 : u8 paren_bal = 0;
957 151703 : for (s32 i = name.size() - 1; i >= 0; i--) {
958 146142 : switch(name[i]) {
959 : case separator:
960 2889 : if (paren_bal == 0) {
961 2812 : last_separator_pos = i;
962 2812 : i = -1; // break out of loop
963 : }
964 2889 : break;
965 : case paren_open:
966 77 : if (paren_bal == 0) {
967 0 : errorstream << "generateImage(): unbalanced parentheses"
968 0 : << "(extranous '(') while generating texture \""
969 0 : << name << "\"" << std::endl;
970 0 : return NULL;
971 : }
972 77 : paren_bal--;
973 77 : break;
974 : case paren_close:
975 77 : paren_bal++;
976 77 : break;
977 : default:
978 143099 : break;
979 : }
980 : }
981 5561 : if (paren_bal > 0) {
982 0 : errorstream << "generateImage(): unbalanced parentheses"
983 0 : << "(missing matching '(') while generating texture \""
984 0 : << name << "\"" << std::endl;
985 0 : return NULL;
986 : }
987 :
988 :
989 5561 : video::IImage *baseimg = NULL;
990 :
991 : /*
992 : If separator was found, make the base image
993 : using a recursive call.
994 : */
995 5561 : if (last_separator_pos != -1) {
996 2812 : baseimg = generateImage(name.substr(0, last_separator_pos));
997 : }
998 :
999 :
1000 5561 : video::IVideoDriver* driver = m_device->getVideoDriver();
1001 5561 : sanity_check(driver);
1002 :
1003 : /*
1004 : Parse out the last part of the name of the image and act
1005 : according to it
1006 : */
1007 :
1008 11122 : std::string last_part_of_name = name.substr(last_separator_pos + 1);
1009 :
1010 : /*
1011 : If this name is enclosed in parentheses, generate it
1012 : and blit it onto the base image
1013 : */
1014 11122 : if (last_part_of_name[0] == paren_open
1015 5561 : && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1016 : std::string name2 = last_part_of_name.substr(1,
1017 134 : last_part_of_name.size() - 2);
1018 67 : video::IImage *tmp = generateImage(name2);
1019 67 : if (!tmp) {
1020 : errorstream << "generateImage(): "
1021 0 : "Failed to generate \"" << name2 << "\""
1022 0 : << std::endl;
1023 0 : return NULL;
1024 : }
1025 67 : core::dimension2d<u32> dim = tmp->getDimension();
1026 67 : if (!baseimg)
1027 32 : baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1028 67 : blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1029 67 : tmp->drop();
1030 5494 : } else if (!generateImagePart(last_part_of_name, baseimg)) {
1031 : // Generate image according to part of name
1032 : errorstream << "generateImage(): "
1033 0 : "Failed to generate \"" << last_part_of_name << "\""
1034 0 : << std::endl;
1035 : }
1036 :
1037 : // If no resulting image, print a warning
1038 5561 : if (baseimg == NULL) {
1039 : errorstream << "generateImage(): baseimg is NULL (attempted to"
1040 0 : " create texture \"" << name << "\")" << std::endl;
1041 : }
1042 :
1043 5561 : return baseimg;
1044 : }
1045 :
1046 : #ifdef __ANDROID__
1047 : #include <GLES/gl.h>
1048 : /**
1049 : * Check and align image to npot2 if required by hardware
1050 : * @param image image to check for npot2 alignment
1051 : * @param driver driver to use for image operations
1052 : * @return image or copy of image aligned to npot2
1053 : */
1054 : video::IImage * Align2Npot2(video::IImage * image,
1055 : video::IVideoDriver* driver)
1056 : {
1057 : if (image == NULL) {
1058 : return image;
1059 : }
1060 :
1061 : core::dimension2d<u32> dim = image->getDimension();
1062 :
1063 : std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1064 : if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1065 : return image;
1066 : }
1067 :
1068 : unsigned int height = npot2(dim.Height);
1069 : unsigned int width = npot2(dim.Width);
1070 :
1071 : if ((dim.Height == height) &&
1072 : (dim.Width == width)) {
1073 : return image;
1074 : }
1075 :
1076 : if (dim.Height > height) {
1077 : height *= 2;
1078 : }
1079 :
1080 : if (dim.Width > width) {
1081 : width *= 2;
1082 : }
1083 :
1084 : video::IImage *targetimage =
1085 : driver->createImage(video::ECF_A8R8G8B8,
1086 : core::dimension2d<u32>(width, height));
1087 :
1088 : if (targetimage != NULL) {
1089 : image->copyToScaling(targetimage);
1090 : }
1091 : image->drop();
1092 : return targetimage;
1093 : }
1094 :
1095 : #endif
1096 :
1097 5494 : bool TextureSource::generateImagePart(std::string part_of_name,
1098 : video::IImage *& baseimg)
1099 : {
1100 5494 : video::IVideoDriver* driver = m_device->getVideoDriver();
1101 5494 : sanity_check(driver);
1102 :
1103 : // Stuff starting with [ are special commands
1104 5494 : if (part_of_name.size() == 0 || part_of_name[0] != '[')
1105 : {
1106 3020 : video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1107 : #ifdef __ANDROID__
1108 : image = Align2Npot2(image, driver);
1109 : #endif
1110 3020 : if (image == NULL) {
1111 2 : if (part_of_name != "") {
1112 0 : if (part_of_name.find("_normal.png") == std::string::npos){
1113 0 : errorstream<<"generateImage(): Could not load image \""
1114 0 : <<part_of_name<<"\""<<" while building texture"<<std::endl;
1115 0 : errorstream<<"generateImage(): Creating a dummy"
1116 0 : <<" image for \""<<part_of_name<<"\""<<std::endl;
1117 : } else {
1118 0 : infostream<<"generateImage(): Could not load normal map \""
1119 0 : <<part_of_name<<"\""<<std::endl;
1120 0 : infostream<<"generateImage(): Creating a dummy"
1121 0 : <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1122 : }
1123 : }
1124 :
1125 : // Just create a dummy image
1126 : //core::dimension2d<u32> dim(2,2);
1127 2 : core::dimension2d<u32> dim(1,1);
1128 2 : image = driver->createImage(video::ECF_A8R8G8B8, dim);
1129 2 : sanity_check(image != NULL);
1130 : /*image->setPixel(0,0, video::SColor(255,255,0,0));
1131 : image->setPixel(1,0, video::SColor(255,0,255,0));
1132 : image->setPixel(0,1, video::SColor(255,0,0,255));
1133 : image->setPixel(1,1, video::SColor(255,255,0,255));*/
1134 6 : image->setPixel(0,0, video::SColor(255,myrand()%256,
1135 6 : myrand()%256,myrand()%256));
1136 : /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1137 : myrand()%256,myrand()%256));
1138 : image->setPixel(0,1, video::SColor(255,myrand()%256,
1139 : myrand()%256,myrand()%256));
1140 : image->setPixel(1,1, video::SColor(255,myrand()%256,
1141 : myrand()%256,myrand()%256));*/
1142 : }
1143 :
1144 : // If base image is NULL, load as base.
1145 3020 : if (baseimg == NULL)
1146 : {
1147 : //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1148 : /*
1149 : Copy it this way to get an alpha channel.
1150 : Otherwise images with alpha cannot be blitted on
1151 : images that don't have alpha in the original file.
1152 : */
1153 2701 : core::dimension2d<u32> dim = image->getDimension();
1154 2701 : baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1155 2701 : image->copyTo(baseimg);
1156 : }
1157 : // Else blit on base.
1158 : else
1159 : {
1160 : //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1161 : // Size of the copied area
1162 319 : core::dimension2d<u32> dim = image->getDimension();
1163 : //core::dimension2d<u32> dim(16,16);
1164 : // Position to copy the blitted to in the base image
1165 319 : core::position2d<s32> pos_to(0,0);
1166 : // Position to copy the blitted from in the blitted image
1167 319 : core::position2d<s32> pos_from(0,0);
1168 : // Blit
1169 : /*image->copyToWithAlpha(baseimg, pos_to,
1170 : core::rect<s32>(pos_from, dim),
1171 : video::SColor(255,255,255,255),
1172 : NULL);*/
1173 319 : blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1174 : }
1175 : //cleanup
1176 3020 : image->drop();
1177 : }
1178 : else
1179 : {
1180 : // A special texture modification
1181 :
1182 : /*infostream<<"generateImage(): generating special "
1183 : <<"modification \""<<part_of_name<<"\""
1184 : <<std::endl;*/
1185 :
1186 : /*
1187 : [crack:N:P
1188 : [cracko:N:P
1189 : Adds a cracking texture
1190 : N = animation frame count, P = crack progression
1191 : */
1192 2474 : if (str_starts_with(part_of_name, "[crack"))
1193 : {
1194 0 : if (baseimg == NULL) {
1195 0 : errorstream<<"generateImagePart(): baseimg == NULL "
1196 0 : <<"for part_of_name=\""<<part_of_name
1197 0 : <<"\", cancelling."<<std::endl;
1198 0 : return false;
1199 : }
1200 :
1201 : // Crack image number and overlay option
1202 0 : bool use_overlay = (part_of_name[6] == 'o');
1203 0 : Strfnd sf(part_of_name);
1204 0 : sf.next(":");
1205 0 : s32 frame_count = stoi(sf.next(":"));
1206 0 : s32 progression = stoi(sf.next(":"));
1207 :
1208 : /*
1209 : Load crack image.
1210 :
1211 : It is an image with a number of cracking stages
1212 : horizontally tiled.
1213 : */
1214 0 : video::IImage *img_crack = m_sourcecache.getOrLoad(
1215 0 : "crack_anylength.png", m_device);
1216 :
1217 0 : if (img_crack && progression >= 0)
1218 : {
1219 0 : draw_crack(img_crack, baseimg,
1220 : use_overlay, frame_count,
1221 0 : progression, driver);
1222 0 : img_crack->drop();
1223 : }
1224 : }
1225 : /*
1226 : [combine:WxH:X,Y=filename:X,Y=filename2
1227 : Creates a bigger texture from an amount of smaller ones
1228 : */
1229 2474 : else if (str_starts_with(part_of_name, "[combine"))
1230 : {
1231 32 : Strfnd sf(part_of_name);
1232 16 : sf.next(":");
1233 16 : u32 w0 = stoi(sf.next("x"));
1234 16 : u32 h0 = stoi(sf.next(":"));
1235 : //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1236 16 : core::dimension2d<u32> dim(w0,h0);
1237 16 : if (baseimg == NULL) {
1238 16 : baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1239 16 : baseimg->fill(video::SColor(0,0,0,0));
1240 : }
1241 1486 : while (sf.atend() == false) {
1242 735 : u32 x = stoi(sf.next(","));
1243 735 : u32 y = stoi(sf.next("="));
1244 1470 : std::string filename = sf.next(":");
1245 735 : infostream<<"Adding \""<<filename
1246 735 : <<"\" to combined ("<<x<<","<<y<<")"
1247 735 : <<std::endl;
1248 735 : video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1249 735 : if (img) {
1250 735 : core::dimension2d<u32> dim = img->getDimension();
1251 735 : infostream<<"Size "<<dim.Width
1252 1470 : <<"x"<<dim.Height<<std::endl;
1253 735 : core::position2d<s32> pos_base(x, y);
1254 : video::IImage *img2 =
1255 735 : driver->createImage(video::ECF_A8R8G8B8, dim);
1256 735 : img->copyTo(img2);
1257 735 : img->drop();
1258 : /*img2->copyToWithAlpha(baseimg, pos_base,
1259 : core::rect<s32>(v2s32(0,0), dim),
1260 : video::SColor(255,255,255,255),
1261 : NULL);*/
1262 735 : blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1263 735 : img2->drop();
1264 : } else {
1265 0 : errorstream << "generateImagePart(): Failed to load image \""
1266 0 : << filename << "\" for [combine" << std::endl;
1267 : }
1268 : }
1269 : }
1270 : /*
1271 : "[brighten"
1272 : */
1273 2458 : else if (str_starts_with(part_of_name, "[brighten"))
1274 : {
1275 13 : if (baseimg == NULL) {
1276 0 : errorstream<<"generateImagePart(): baseimg==NULL "
1277 0 : <<"for part_of_name=\""<<part_of_name
1278 0 : <<"\", cancelling."<<std::endl;
1279 0 : return false;
1280 : }
1281 :
1282 13 : brighten(baseimg);
1283 : }
1284 : /*
1285 : "[noalpha"
1286 : Make image completely opaque.
1287 : Used for the leaves texture when in old leaves mode, so
1288 : that the transparent parts don't look completely black
1289 : when simple alpha channel is used for rendering.
1290 : */
1291 2445 : else if (str_starts_with(part_of_name, "[noalpha"))
1292 : {
1293 0 : if (baseimg == NULL){
1294 0 : errorstream<<"generateImagePart(): baseimg==NULL "
1295 0 : <<"for part_of_name=\""<<part_of_name
1296 0 : <<"\", cancelling."<<std::endl;
1297 0 : return false;
1298 : }
1299 :
1300 0 : core::dimension2d<u32> dim = baseimg->getDimension();
1301 :
1302 : // Set alpha to full
1303 0 : for (u32 y=0; y<dim.Height; y++)
1304 0 : for (u32 x=0; x<dim.Width; x++)
1305 : {
1306 0 : video::SColor c = baseimg->getPixel(x,y);
1307 0 : c.setAlpha(255);
1308 0 : baseimg->setPixel(x,y,c);
1309 : }
1310 : }
1311 : /*
1312 : "[makealpha:R,G,B"
1313 : Convert one color to transparent.
1314 : */
1315 2445 : else if (str_starts_with(part_of_name, "[makealpha:"))
1316 : {
1317 44 : if (baseimg == NULL) {
1318 0 : errorstream<<"generateImagePart(): baseimg == NULL "
1319 0 : <<"for part_of_name=\""<<part_of_name
1320 0 : <<"\", cancelling."<<std::endl;
1321 0 : return false;
1322 : }
1323 :
1324 88 : Strfnd sf(part_of_name.substr(11));
1325 44 : u32 r1 = stoi(sf.next(","));
1326 44 : u32 g1 = stoi(sf.next(","));
1327 44 : u32 b1 = stoi(sf.next(""));
1328 88 : std::string filename = sf.next("");
1329 :
1330 44 : core::dimension2d<u32> dim = baseimg->getDimension();
1331 :
1332 : /*video::IImage *oldbaseimg = baseimg;
1333 : baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1334 : oldbaseimg->copyTo(baseimg);
1335 : oldbaseimg->drop();*/
1336 :
1337 : // Set alpha to full
1338 1932 : for (u32 y=0; y<dim.Height; y++)
1339 366176 : for (u32 x=0; x<dim.Width; x++)
1340 : {
1341 364288 : video::SColor c = baseimg->getPixel(x,y);
1342 364288 : u32 r = c.getRed();
1343 364288 : u32 g = c.getGreen();
1344 364288 : u32 b = c.getBlue();
1345 364288 : if (!(r == r1 && g == g1 && b == b1))
1346 10816 : continue;
1347 353472 : c.setAlpha(0);
1348 353472 : baseimg->setPixel(x,y,c);
1349 : }
1350 : }
1351 : /*
1352 : "[transformN"
1353 : Rotates and/or flips the image.
1354 :
1355 : N can be a number (between 0 and 7) or a transform name.
1356 : Rotations are counter-clockwise.
1357 : 0 I identity
1358 : 1 R90 rotate by 90 degrees
1359 : 2 R180 rotate by 180 degrees
1360 : 3 R270 rotate by 270 degrees
1361 : 4 FX flip X
1362 : 5 FXR90 flip X then rotate by 90 degrees
1363 : 6 FY flip Y
1364 : 7 FYR90 flip Y then rotate by 90 degrees
1365 :
1366 : Note: Transform names can be concatenated to produce
1367 : their product (applies the first then the second).
1368 : The resulting transform will be equivalent to one of the
1369 : eight existing ones, though (see: dihedral group).
1370 : */
1371 2401 : else if (str_starts_with(part_of_name, "[transform"))
1372 : {
1373 134 : if (baseimg == NULL) {
1374 0 : errorstream<<"generateImagePart(): baseimg == NULL "
1375 0 : <<"for part_of_name=\""<<part_of_name
1376 0 : <<"\", cancelling."<<std::endl;
1377 0 : return false;
1378 : }
1379 :
1380 134 : u32 transform = parseImageTransform(part_of_name.substr(10));
1381 : core::dimension2d<u32> dim = imageTransformDimension(
1382 134 : transform, baseimg->getDimension());
1383 268 : video::IImage *image = driver->createImage(
1384 402 : baseimg->getColorFormat(), dim);
1385 134 : sanity_check(image != NULL);
1386 134 : imageTransform(transform, baseimg, image);
1387 134 : baseimg->drop();
1388 134 : baseimg = image;
1389 : }
1390 : /*
1391 : [inventorycube{topimage{leftimage{rightimage
1392 : In every subimage, replace ^ with &.
1393 : Create an "inventory cube".
1394 : NOTE: This should be used only on its own.
1395 : Example (a grass block (not actually used in game):
1396 : "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1397 : */
1398 2267 : else if (str_starts_with(part_of_name, "[inventorycube"))
1399 : {
1400 0 : if (baseimg != NULL){
1401 0 : errorstream<<"generateImagePart(): baseimg != NULL "
1402 0 : <<"for part_of_name=\""<<part_of_name
1403 0 : <<"\", cancelling."<<std::endl;
1404 0 : return false;
1405 : }
1406 :
1407 0 : str_replace(part_of_name, '&', '^');
1408 0 : Strfnd sf(part_of_name);
1409 0 : sf.next("{");
1410 0 : std::string imagename_top = sf.next("{");
1411 0 : std::string imagename_left = sf.next("{");
1412 0 : std::string imagename_right = sf.next("{");
1413 :
1414 : // Generate images for the faces of the cube
1415 0 : video::IImage *img_top = generateImage(imagename_top);
1416 0 : video::IImage *img_left = generateImage(imagename_left);
1417 0 : video::IImage *img_right = generateImage(imagename_right);
1418 :
1419 0 : if (img_top == NULL || img_left == NULL || img_right == NULL) {
1420 0 : errorstream << "generateImagePart(): Failed to create textures"
1421 0 : << " for inventorycube \"" << part_of_name << "\""
1422 0 : << std::endl;
1423 0 : baseimg = generateImage(imagename_top);
1424 0 : return true;
1425 : }
1426 :
1427 : #ifdef __ANDROID__
1428 : assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1429 : assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1430 :
1431 : assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1432 : assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1433 :
1434 : assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1435 : assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1436 : #endif
1437 :
1438 : // Create textures from images
1439 0 : video::ITexture *texture_top = driver->addTexture(
1440 0 : (imagename_top + "__temp__").c_str(), img_top);
1441 0 : video::ITexture *texture_left = driver->addTexture(
1442 0 : (imagename_left + "__temp__").c_str(), img_left);
1443 0 : video::ITexture *texture_right = driver->addTexture(
1444 0 : (imagename_right + "__temp__").c_str(), img_right);
1445 0 : FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1446 :
1447 : // Drop images
1448 0 : img_top->drop();
1449 0 : img_left->drop();
1450 0 : img_right->drop();
1451 :
1452 : /*
1453 : Draw a cube mesh into a render target texture
1454 : */
1455 0 : scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1456 0 : setMeshColor(cube, video::SColor(255, 255, 255, 255));
1457 0 : cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1458 0 : cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1459 0 : cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1460 0 : cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1461 0 : cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1462 0 : cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1463 :
1464 0 : TextureFromMeshParams params;
1465 0 : params.mesh = cube;
1466 0 : params.dim.set(64, 64);
1467 0 : params.rtt_texture_name = part_of_name + "_RTT";
1468 : // We will delete the rtt texture ourselves
1469 0 : params.delete_texture_on_shutdown = false;
1470 0 : params.camera_position.set(0, 1.0, -1.5);
1471 0 : params.camera_position.rotateXZBy(45);
1472 0 : params.camera_lookat.set(0, 0, 0);
1473 : // Set orthogonal projection
1474 : params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1475 0 : 1.65, 1.65, 0, 100);
1476 :
1477 0 : params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1478 0 : params.light_position.set(10, 100, -50);
1479 0 : params.light_color.set(1.0, 0.5, 0.5, 0.5);
1480 0 : params.light_radius = 1000;
1481 :
1482 0 : video::ITexture *rtt = generateTextureFromMesh(params);
1483 :
1484 : // Drop mesh
1485 0 : cube->drop();
1486 :
1487 : // Free textures
1488 0 : driver->removeTexture(texture_top);
1489 0 : driver->removeTexture(texture_left);
1490 0 : driver->removeTexture(texture_right);
1491 :
1492 0 : if (rtt == NULL) {
1493 0 : baseimg = generateImage(imagename_top);
1494 0 : return true;
1495 : }
1496 :
1497 : // Create image of render target
1498 0 : video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1499 0 : FATAL_ERROR_IF(!image, "Could not create image of render target");
1500 :
1501 : // Cleanup texture
1502 0 : driver->removeTexture(rtt);
1503 :
1504 0 : baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1505 :
1506 0 : if (image) {
1507 0 : image->copyTo(baseimg);
1508 0 : image->drop();
1509 : }
1510 : }
1511 : /*
1512 : [lowpart:percent:filename
1513 : Adds the lower part of a texture
1514 : */
1515 2267 : else if (str_starts_with(part_of_name, "[lowpart:"))
1516 : {
1517 0 : Strfnd sf(part_of_name);
1518 0 : sf.next(":");
1519 0 : u32 percent = stoi(sf.next(":"));
1520 0 : std::string filename = sf.next(":");
1521 : //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1522 :
1523 0 : if (baseimg == NULL)
1524 0 : baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1525 0 : video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1526 0 : if (img)
1527 : {
1528 0 : core::dimension2d<u32> dim = img->getDimension();
1529 0 : core::position2d<s32> pos_base(0, 0);
1530 : video::IImage *img2 =
1531 0 : driver->createImage(video::ECF_A8R8G8B8, dim);
1532 0 : img->copyTo(img2);
1533 0 : img->drop();
1534 0 : core::position2d<s32> clippos(0, 0);
1535 0 : clippos.Y = dim.Height * (100-percent) / 100;
1536 0 : core::dimension2d<u32> clipdim = dim;
1537 0 : clipdim.Height = clipdim.Height * percent / 100 + 1;
1538 0 : core::rect<s32> cliprect(clippos, clipdim);
1539 0 : img2->copyToWithAlpha(baseimg, pos_base,
1540 : core::rect<s32>(v2s32(0,0), dim),
1541 : video::SColor(255,255,255,255),
1542 0 : &cliprect);
1543 0 : img2->drop();
1544 : }
1545 : }
1546 : /*
1547 : [verticalframe:N:I
1548 : Crops a frame of a vertical animation.
1549 : N = frame count, I = frame index
1550 : */
1551 2267 : else if (str_starts_with(part_of_name, "[verticalframe:"))
1552 : {
1553 546 : Strfnd sf(part_of_name);
1554 273 : sf.next(":");
1555 273 : u32 frame_count = stoi(sf.next(":"));
1556 273 : u32 frame_index = stoi(sf.next(":"));
1557 :
1558 273 : if (baseimg == NULL){
1559 0 : errorstream<<"generateImagePart(): baseimg != NULL "
1560 0 : <<"for part_of_name=\""<<part_of_name
1561 0 : <<"\", cancelling."<<std::endl;
1562 0 : return false;
1563 : }
1564 :
1565 273 : v2u32 frame_size = baseimg->getDimension();
1566 273 : frame_size.Y /= frame_count;
1567 :
1568 546 : video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1569 546 : frame_size);
1570 273 : if (!img){
1571 0 : errorstream<<"generateImagePart(): Could not create image "
1572 0 : <<"for part_of_name=\""<<part_of_name
1573 0 : <<"\", cancelling."<<std::endl;
1574 0 : return false;
1575 : }
1576 :
1577 : // Fill target image with transparency
1578 273 : img->fill(video::SColor(0,0,0,0));
1579 :
1580 273 : core::dimension2d<u32> dim = frame_size;
1581 273 : core::position2d<s32> pos_dst(0, 0);
1582 273 : core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1583 546 : baseimg->copyToWithAlpha(img, pos_dst,
1584 : core::rect<s32>(pos_src, dim),
1585 : video::SColor(255,255,255,255),
1586 546 : NULL);
1587 : // Replace baseimg
1588 273 : baseimg->drop();
1589 273 : baseimg = img;
1590 : }
1591 : /*
1592 : [mask:filename
1593 : Applies a mask to an image
1594 : */
1595 1994 : else if (str_starts_with(part_of_name, "[mask:"))
1596 : {
1597 0 : if (baseimg == NULL) {
1598 0 : errorstream << "generateImage(): baseimg == NULL "
1599 0 : << "for part_of_name=\"" << part_of_name
1600 0 : << "\", cancelling." << std::endl;
1601 0 : return false;
1602 : }
1603 0 : Strfnd sf(part_of_name);
1604 0 : sf.next(":");
1605 0 : std::string filename = sf.next(":");
1606 :
1607 0 : video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1608 0 : if (img) {
1609 0 : apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1610 0 : img->getDimension());
1611 : } else {
1612 0 : errorstream << "generateImage(): Failed to load \""
1613 0 : << filename << "\".";
1614 : }
1615 : }
1616 : /*
1617 : [colorize:color
1618 : Overlays image with given color
1619 : color = color as ColorString
1620 : */
1621 1994 : else if (str_starts_with(part_of_name, "[colorize:"))
1622 : {
1623 400 : Strfnd sf(part_of_name);
1624 200 : sf.next(":");
1625 400 : std::string color_str = sf.next(":");
1626 400 : std::string ratio_str = sf.next(":");
1627 :
1628 200 : if (baseimg == NULL) {
1629 0 : errorstream << "generateImagePart(): baseimg != NULL "
1630 0 : << "for part_of_name=\"" << part_of_name
1631 0 : << "\", cancelling." << std::endl;
1632 0 : return false;
1633 : }
1634 :
1635 200 : video::SColor color;
1636 200 : int ratio = -1;
1637 :
1638 200 : if (!parseColorString(color_str, color, false))
1639 0 : return false;
1640 :
1641 200 : if (is_number(ratio_str))
1642 199 : ratio = mystoi(ratio_str, 0, 255);
1643 :
1644 200 : core::dimension2d<u32> dim = baseimg->getDimension();
1645 200 : video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1646 :
1647 200 : if (!img) {
1648 0 : errorstream << "generateImagePart(): Could not create image "
1649 0 : << "for part_of_name=\"" << part_of_name
1650 0 : << "\", cancelling." << std::endl;
1651 0 : return false;
1652 : }
1653 :
1654 200 : img->fill(video::SColor(color));
1655 : // Overlay the colored image
1656 200 : blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1657 200 : img->drop();
1658 : }
1659 1794 : else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1660 : {
1661 : // Apply the "clean transparent" filter, if configured.
1662 1794 : if (g_settings->getBool("texture_clean_transparent"))
1663 0 : imageCleanTransparent(baseimg, 127);
1664 :
1665 : /* Upscale textures to user's requested minimum size. This is a trick to make
1666 : * filters look as good on low-res textures as on high-res ones, by making
1667 : * low-res textures BECOME high-res ones. This is helpful for worlds that
1668 : * mix high- and low-res textures, or for mods with least-common-denominator
1669 : * textures that don't have the resources to offer high-res alternatives.
1670 : */
1671 1794 : s32 scaleto = g_settings->getS32("texture_min_size");
1672 1794 : if (scaleto > 1) {
1673 1794 : const core::dimension2d<u32> dim = baseimg->getDimension();
1674 :
1675 : /* Calculate scaling needed to make the shortest texture dimension
1676 : * equal to the target minimum. If e.g. this is a vertical frames
1677 : * animation, the short dimension will be the real size.
1678 : */
1679 1794 : u32 xscale = scaleto / dim.Width;
1680 1794 : u32 yscale = scaleto / dim.Height;
1681 1794 : u32 scale = (xscale > yscale) ? xscale : yscale;
1682 :
1683 : // Never downscale; only scale up by 2x or more.
1684 1794 : if (scale > 1) {
1685 1472 : u32 w = scale * dim.Width;
1686 1472 : u32 h = scale * dim.Height;
1687 1472 : const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1688 2944 : video::IImage *newimg = driver->createImage(
1689 4416 : baseimg->getColorFormat(), newdim);
1690 1472 : baseimg->copyToScaling(newimg);
1691 1472 : baseimg->drop();
1692 1472 : baseimg = newimg;
1693 : }
1694 : }
1695 : }
1696 : else
1697 : {
1698 : errorstream << "generateImagePart(): Invalid "
1699 0 : " modification: \"" << part_of_name << "\"" << std::endl;
1700 : }
1701 : }
1702 :
1703 5494 : return true;
1704 : }
1705 :
1706 : /*
1707 : Draw an image on top of an another one, using the alpha channel of the
1708 : source image
1709 :
1710 : This exists because IImage::copyToWithAlpha() doesn't seem to always
1711 : work.
1712 : */
1713 1121 : static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1714 : v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1715 : {
1716 53014 : for (u32 y0=0; y0<size.Y; y0++)
1717 17230867 : for (u32 x0=0; x0<size.X; x0++)
1718 : {
1719 17178974 : s32 src_x = src_pos.X + x0;
1720 17178974 : s32 src_y = src_pos.Y + y0;
1721 17178974 : s32 dst_x = dst_pos.X + x0;
1722 17178974 : s32 dst_y = dst_pos.Y + y0;
1723 17178974 : video::SColor src_c = src->getPixel(src_x, src_y);
1724 17178974 : video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1725 17178974 : dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1726 17178974 : dst->setPixel(dst_x, dst_y, dst_c);
1727 : }
1728 1121 : }
1729 :
1730 : /*
1731 : Draw an image on top of an another one, using the alpha channel of the
1732 : source image; only modify fully opaque pixels in destinaion
1733 : */
1734 0 : static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1735 : v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1736 : {
1737 0 : for (u32 y0=0; y0<size.Y; y0++)
1738 0 : for (u32 x0=0; x0<size.X; x0++)
1739 : {
1740 0 : s32 src_x = src_pos.X + x0;
1741 0 : s32 src_y = src_pos.Y + y0;
1742 0 : s32 dst_x = dst_pos.X + x0;
1743 0 : s32 dst_y = dst_pos.Y + y0;
1744 0 : video::SColor src_c = src->getPixel(src_x, src_y);
1745 0 : video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1746 0 : if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1747 : {
1748 0 : dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1749 0 : dst->setPixel(dst_x, dst_y, dst_c);
1750 : }
1751 : }
1752 0 : }
1753 :
1754 : /*
1755 : Draw an image on top of an another one, using the specified ratio
1756 : modify all partially-opaque pixels in the destination.
1757 : */
1758 200 : static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1759 : v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1760 : {
1761 4696 : for (u32 y0 = 0; y0 < size.Y; y0++)
1762 153232 : for (u32 x0 = 0; x0 < size.X; x0++)
1763 : {
1764 148736 : s32 src_x = src_pos.X + x0;
1765 148736 : s32 src_y = src_pos.Y + y0;
1766 148736 : s32 dst_x = dst_pos.X + x0;
1767 148736 : s32 dst_y = dst_pos.Y + y0;
1768 148736 : video::SColor src_c = src->getPixel(src_x, src_y);
1769 148736 : video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1770 148736 : if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1771 : {
1772 116512 : if (ratio == -1)
1773 60 : dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1774 : else
1775 116452 : dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1776 116512 : dst->setPixel(dst_x, dst_y, dst_c);
1777 : }
1778 : }
1779 200 : }
1780 :
1781 : /*
1782 : Apply mask to destination
1783 : */
1784 0 : static void apply_mask(video::IImage *mask, video::IImage *dst,
1785 : v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1786 : {
1787 0 : for (u32 y0 = 0; y0 < size.Y; y0++) {
1788 0 : for (u32 x0 = 0; x0 < size.X; x0++) {
1789 0 : s32 mask_x = x0 + mask_pos.X;
1790 0 : s32 mask_y = y0 + mask_pos.Y;
1791 0 : s32 dst_x = x0 + dst_pos.X;
1792 0 : s32 dst_y = y0 + dst_pos.Y;
1793 0 : video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1794 0 : video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1795 0 : dst_c.color &= mask_c.color;
1796 0 : dst->setPixel(dst_x, dst_y, dst_c);
1797 : }
1798 : }
1799 0 : }
1800 :
1801 0 : static void draw_crack(video::IImage *crack, video::IImage *dst,
1802 : bool use_overlay, s32 frame_count, s32 progression,
1803 : video::IVideoDriver *driver)
1804 : {
1805 : // Dimension of destination image
1806 0 : core::dimension2d<u32> dim_dst = dst->getDimension();
1807 : // Dimension of original image
1808 0 : core::dimension2d<u32> dim_crack = crack->getDimension();
1809 : // Count of crack stages
1810 0 : s32 crack_count = dim_crack.Height / dim_crack.Width;
1811 : // Limit frame_count
1812 0 : if (frame_count > (s32) dim_dst.Height)
1813 0 : frame_count = dim_dst.Height;
1814 0 : if (frame_count < 1)
1815 0 : frame_count = 1;
1816 : // Limit progression
1817 0 : if (progression > crack_count-1)
1818 0 : progression = crack_count-1;
1819 : // Dimension of a single crack stage
1820 : core::dimension2d<u32> dim_crack_cropped(
1821 : dim_crack.Width,
1822 : dim_crack.Width
1823 0 : );
1824 : // Dimension of the scaled crack stage,
1825 : // which is the same as the dimension of a single destination frame
1826 : core::dimension2d<u32> dim_crack_scaled(
1827 : dim_dst.Width,
1828 0 : dim_dst.Height / frame_count
1829 0 : );
1830 : // Create cropped and scaled crack images
1831 : video::IImage *crack_cropped = driver->createImage(
1832 0 : video::ECF_A8R8G8B8, dim_crack_cropped);
1833 : video::IImage *crack_scaled = driver->createImage(
1834 0 : video::ECF_A8R8G8B8, dim_crack_scaled);
1835 :
1836 0 : if (crack_cropped && crack_scaled)
1837 : {
1838 : // Crop crack image
1839 0 : v2s32 pos_crack(0, progression*dim_crack.Width);
1840 0 : crack->copyTo(crack_cropped,
1841 : v2s32(0,0),
1842 0 : core::rect<s32>(pos_crack, dim_crack_cropped));
1843 : // Scale crack image by copying
1844 0 : crack_cropped->copyToScaling(crack_scaled);
1845 : // Copy or overlay crack image onto each frame
1846 0 : for (s32 i = 0; i < frame_count; ++i)
1847 : {
1848 0 : v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1849 0 : if (use_overlay)
1850 : {
1851 0 : blit_with_alpha_overlay(crack_scaled, dst,
1852 : v2s32(0,0), dst_pos,
1853 0 : dim_crack_scaled);
1854 : }
1855 : else
1856 : {
1857 0 : blit_with_alpha(crack_scaled, dst,
1858 : v2s32(0,0), dst_pos,
1859 0 : dim_crack_scaled);
1860 : }
1861 : }
1862 : }
1863 :
1864 0 : if (crack_scaled)
1865 0 : crack_scaled->drop();
1866 :
1867 0 : if (crack_cropped)
1868 0 : crack_cropped->drop();
1869 0 : }
1870 :
1871 13 : void brighten(video::IImage *image)
1872 : {
1873 13 : if (image == NULL)
1874 0 : return;
1875 :
1876 13 : core::dimension2d<u32> dim = image->getDimension();
1877 :
1878 221 : for (u32 y=0; y<dim.Height; y++)
1879 3536 : for (u32 x=0; x<dim.Width; x++)
1880 : {
1881 3328 : video::SColor c = image->getPixel(x,y);
1882 3328 : c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1883 3328 : c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1884 3328 : c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1885 3328 : image->setPixel(x,y,c);
1886 : }
1887 : }
1888 :
1889 134 : u32 parseImageTransform(const std::string& s)
1890 : {
1891 134 : int total_transform = 0;
1892 :
1893 268 : std::string transform_names[8];
1894 134 : transform_names[0] = "i";
1895 134 : transform_names[1] = "r90";
1896 134 : transform_names[2] = "r180";
1897 134 : transform_names[3] = "r270";
1898 134 : transform_names[4] = "fx";
1899 134 : transform_names[6] = "fy";
1900 :
1901 134 : std::size_t pos = 0;
1902 402 : while(pos < s.size())
1903 : {
1904 134 : int transform = -1;
1905 563 : for (int i = 0; i <= 7; ++i)
1906 : {
1907 563 : const std::string &name_i = transform_names[i];
1908 :
1909 563 : if (s[pos] == ('0' + i))
1910 : {
1911 0 : transform = i;
1912 0 : pos++;
1913 0 : break;
1914 : }
1915 2225 : else if (!(name_i.empty()) &&
1916 2171 : lowercase(s.substr(pos, name_i.size())) == name_i)
1917 : {
1918 134 : transform = i;
1919 134 : pos += name_i.size();
1920 134 : break;
1921 : }
1922 : }
1923 134 : if (transform < 0)
1924 0 : break;
1925 :
1926 : // Multiply total_transform and transform in the group D4
1927 134 : int new_total = 0;
1928 134 : if (transform < 4)
1929 68 : new_total = (transform + total_transform) % 4;
1930 : else
1931 66 : new_total = (transform - total_transform + 8) % 4;
1932 134 : if ((transform >= 4) ^ (total_transform >= 4))
1933 66 : new_total += 4;
1934 :
1935 134 : total_transform = new_total;
1936 : }
1937 268 : return total_transform;
1938 : }
1939 :
1940 134 : core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1941 : {
1942 134 : if (transform % 2 == 0)
1943 81 : return dim;
1944 : else
1945 53 : return core::dimension2d<u32>(dim.Height, dim.Width);
1946 : }
1947 :
1948 134 : void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1949 : {
1950 134 : if (src == NULL || dst == NULL)
1951 0 : return;
1952 :
1953 134 : core::dimension2d<u32> dstdim = dst->getDimension();
1954 :
1955 : // Pre-conditions
1956 : assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1957 : assert(transform <= 7);
1958 :
1959 : /*
1960 : Compute the transformation from source coordinates (sx,sy)
1961 : to destination coordinates (dx,dy).
1962 : */
1963 134 : int sxn = 0;
1964 134 : int syn = 2;
1965 134 : if (transform == 0) // identity
1966 0 : sxn = 0, syn = 2; // sx = dx, sy = dy
1967 134 : else if (transform == 1) // rotate by 90 degrees ccw
1968 39 : sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1969 95 : else if (transform == 2) // rotate by 180 degrees
1970 15 : sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1971 80 : else if (transform == 3) // rotate by 270 degrees ccw
1972 14 : sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1973 66 : else if (transform == 4) // flip x
1974 39 : sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1975 27 : else if (transform == 5) // flip x then rotate by 90 degrees ccw
1976 0 : sxn = 2, syn = 0; // sx = dy, sy = dx
1977 27 : else if (transform == 6) // flip y
1978 27 : sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1979 0 : else if (transform == 7) // flip y then rotate by 90 degrees ccw
1980 0 : sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1981 :
1982 2614 : for (u32 dy=0; dy<dstdim.Height; dy++)
1983 71344 : for (u32 dx=0; dx<dstdim.Width; dx++)
1984 : {
1985 68864 : u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1986 68864 : u32 sx = entries[sxn];
1987 68864 : u32 sy = entries[syn];
1988 68864 : video::SColor c = src->getPixel(sx,sy);
1989 68864 : dst->setPixel(dx,dy,c);
1990 : }
1991 : }
1992 :
1993 62340 : video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1994 : {
1995 : u32 id;
1996 62340 : if (isKnownSourceImage("override_normal.png"))
1997 0 : return getTexture("override_normal.png", &id);
1998 124680 : std::string fname_base = name;
1999 124680 : std::string normal_ext = "_normal.png";
2000 62340 : size_t pos = fname_base.find(".");
2001 124680 : std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2002 62340 : if (isKnownSourceImage(fname_normal)) {
2003 : // look for image extension and replace it
2004 0 : size_t i = 0;
2005 0 : while ((i = fname_base.find(".", i)) != std::string::npos) {
2006 0 : fname_base.replace(i, 4, normal_ext);
2007 0 : i += normal_ext.length();
2008 : }
2009 0 : return getTexture(fname_base, &id);
2010 : }
2011 62340 : return NULL;
2012 : }
2013 :
2014 5144 : video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2015 : {
2016 5144 : video::IVideoDriver *driver = m_device->getVideoDriver();
2017 5144 : video::SColor c(0, 0, 0, 0);
2018 : u32 id;
2019 5144 : video::ITexture *texture = getTexture(name, &id);
2020 10288 : video::IImage *image = driver->createImage(texture,
2021 : core::position2d<s32>(0, 0),
2022 10288 : texture->getOriginalSize());
2023 5144 : u32 total = 0;
2024 5144 : u32 tR = 0;
2025 5144 : u32 tG = 0;
2026 5144 : u32 tB = 0;
2027 5144 : core::dimension2d<u32> dim = image->getDimension();
2028 5144 : u16 step = 1;
2029 5144 : if (dim.Width > 16)
2030 445 : step = dim.Width / 16;
2031 86676 : for (u16 x = 0; x < dim.Width; x += step) {
2032 1385606 : for (u16 y = 0; y < dim.Width; y += step) {
2033 1304074 : c = image->getPixel(x,y);
2034 1304074 : if (c.getAlpha() > 0) {
2035 1159976 : total++;
2036 1159976 : tR += c.getRed();
2037 1159976 : tG += c.getGreen();
2038 1159976 : tB += c.getBlue();
2039 : }
2040 : }
2041 : }
2042 5144 : image->drop();
2043 5144 : if (total > 0) {
2044 5118 : c.setRed(tR / total);
2045 5118 : c.setGreen(tG / total);
2046 5118 : c.setBlue(tB / total);
2047 : }
2048 5144 : c.setAlpha(255);
2049 5144 : return c;
2050 3 : }
|