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.
23 #include <ICameraSceneNode.h>
24 #include <IrrCompileConfig.h>
25 #include "util/string.h"
26 #include "util/container.h"
27 #include "util/thread.h"
32 #include "util/strfnd.h"
33 #include "imagefilters.h"
34 #include "guiscalingfilter.h"
35 #include "renderingengine.h"
39 #ifdef _IRR_COMPILE_WITH_OGLES1_
42 #include <GLES2/gl2.h>
47 A cache from texture name to texture path
49 MutexedMap
<std::string
, std::string
> g_texturename_to_path_cache
;
52 Replaces the filename extension.
54 std::string image = "a/image.png"
55 replace_ext(image, "jpg")
56 -> image = "a/image.jpg"
57 Returns true on success.
59 static bool replace_ext(std::string
&path
, const char *ext
)
63 // Find place of last dot, fail if \ or / found.
65 for (s32 i
=path
.size()-1; i
>=0; i
--)
73 if (path
[i
] == '\\' || path
[i
] == '/')
76 // If not found, return an empty string
79 // Else make the new path
80 path
= path
.substr(0, last_dot_i
+1) + ext
;
85 Find out the full path of an image by trying different filename
90 std::string
getImagePath(std::string path
)
92 // A NULL-ended list of possible image extensions
93 const char *extensions
[] = {
94 "png", "jpg", "bmp", "tga",
95 "pcx", "ppm", "psd", "wal", "rgb",
98 // If there is no extension, add one
99 if (removeStringEnd(path
, extensions
).empty())
100 path
= path
+ ".png";
101 // Check paths until something is found to exist
102 const char **ext
= extensions
;
104 bool r
= replace_ext(path
, *ext
);
107 if (fs::PathExists(path
))
110 while((++ext
) != NULL
);
116 Gets the path to a texture by first checking if the texture exists
117 in texture_path and if not, using the data path.
119 Checks all supported extensions by replacing the original extension.
121 If not found, returns "".
123 Utilizes a thread-safe cache.
125 std::string
getTexturePath(const std::string
&filename
, bool *is_base_pack
)
127 std::string fullpath
;
129 // This can set a wrong value on cached textures, but is irrelevant because
130 // is_base_pack is only passed when initializing the textures the first time
132 *is_base_pack
= false;
136 bool incache
= g_texturename_to_path_cache
.get(filename
, &fullpath
);
141 Check from texture_path
143 for (const auto &path
: getTextureDirs()) {
144 std::string testpath
= path
+ DIR_DELIM
;
145 testpath
.append(filename
);
146 // Check all filename extensions. Returns "" if not found.
147 fullpath
= getImagePath(testpath
);
148 if (!fullpath
.empty())
153 Check from default data directory
155 if (fullpath
.empty())
157 std::string base_path
= porting::path_share
+ DIR_DELIM
+ "textures"
158 + DIR_DELIM
+ "base" + DIR_DELIM
+ "pack";
159 std::string testpath
= base_path
+ DIR_DELIM
+ filename
;
160 // Check all filename extensions. Returns "" if not found.
161 fullpath
= getImagePath(testpath
);
162 if (is_base_pack
&& !fullpath
.empty())
163 *is_base_pack
= true;
166 // Add to cache (also an empty result is cached)
167 g_texturename_to_path_cache
.set(filename
, fullpath
);
173 void clearTextureNameCache()
175 g_texturename_to_path_cache
.clear();
179 Stores internal information about a texture.
185 video::ITexture
*texture
;
188 const std::string
&name_
,
189 video::ITexture
*texture_
=NULL
198 SourceImageCache: A cache used for storing source images.
201 class SourceImageCache
204 ~SourceImageCache() {
205 for (auto &m_image
: m_images
) {
206 m_image
.second
->drop();
210 void insert(const std::string
&name
, video::IImage
*img
, bool prefer_local
)
212 assert(img
); // Pre-condition
214 std::map
<std::string
, video::IImage
*>::iterator n
;
215 n
= m_images
.find(name
);
216 if (n
!= m_images
.end()){
221 video::IImage
* toadd
= img
;
222 bool need_to_grab
= true;
224 // Try to use local texture instead if asked to
227 std::string path
= getTexturePath(name
, &is_base_pack
);
229 if (!path
.empty() && !is_base_pack
) {
230 video::IImage
*img2
= RenderingEngine::get_video_driver()->
231 createImageFromFile(path
.c_str());
234 need_to_grab
= false;
241 m_images
[name
] = toadd
;
243 video::IImage
* get(const std::string
&name
)
245 std::map
<std::string
, video::IImage
*>::iterator n
;
246 n
= m_images
.find(name
);
247 if (n
!= m_images
.end())
251 // Primarily fetches from cache, secondarily tries to read from filesystem
252 video::IImage
*getOrLoad(const std::string
&name
)
254 std::map
<std::string
, video::IImage
*>::iterator n
;
255 n
= m_images
.find(name
);
256 if (n
!= m_images
.end()){
257 n
->second
->grab(); // Grab for caller
260 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
261 std::string path
= getTexturePath(name
);
263 infostream
<<"SourceImageCache::getOrLoad(): No path found for \""
264 <<name
<<"\""<<std::endl
;
267 infostream
<<"SourceImageCache::getOrLoad(): Loading path \""<<path
269 video::IImage
*img
= driver
->createImageFromFile(path
.c_str());
272 m_images
[name
] = img
;
273 img
->grab(); // Grab for caller
278 std::map
<std::string
, video::IImage
*> m_images
;
285 class TextureSource
: public IWritableTextureSource
289 virtual ~TextureSource();
293 Now, assume a texture with the id 1 exists, and has the name
294 "stone.png^mineral1".
295 Then a random thread calls getTextureId for a texture called
296 "stone.png^mineral1^crack0".
297 ...Now, WTF should happen? Well:
298 - getTextureId strips off stuff recursively from the end until
299 the remaining part is found, or nothing is left when
300 something is stripped out
302 But it is slow to search for textures by names and modify them
304 - ContentFeatures is made to contain ids for the basic plain
306 - Crack textures can be slow by themselves, but the framework
310 - Assume a texture with the id 1 exists, and has the name
311 "stone.png^mineral_coal.png".
312 - Now getNodeTile() stumbles upon a node which uses
313 texture id 1, and determines that MATERIAL_FLAG_CRACK
314 must be applied to the tile
315 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
316 has received the current crack level 0 from the client. It
317 finds out the name of the texture with getTextureName(1),
318 appends "^crack0" to it and gets a new texture id with
319 getTextureId("stone.png^mineral_coal.png^crack0").
324 Gets a texture id from cache or
325 - if main thread, generates the texture, adds to cache and returns id.
326 - if other thread, adds to request queue and waits for main thread.
328 The id 0 points to a NULL texture. It is returned in case of error.
330 u32
getTextureId(const std::string
&name
);
332 // Finds out the name of a cached texture.
333 std::string
getTextureName(u32 id
);
336 If texture specified by the name pointed by the id doesn't
337 exist, create it, then return the cached texture.
339 Can be called from any thread. If called from some other thread
340 and not found in cache, the call is queued to the main thread
343 video::ITexture
* getTexture(u32 id
);
345 video::ITexture
* getTexture(const std::string
&name
, u32
*id
= NULL
);
348 Get a texture specifically intended for mesh
349 application, i.e. not HUD, compositing, or other 2D
350 use. This texture may be a different size and may
351 have had additional filters applied.
353 video::ITexture
* getTextureForMesh(const std::string
&name
, u32
*id
);
355 virtual Palette
* getPalette(const std::string
&name
);
357 bool isKnownSourceImage(const std::string
&name
)
359 bool is_known
= false;
360 bool cache_found
= m_source_image_existence
.get(name
, &is_known
);
363 // Not found in cache; find out if a local file exists
364 is_known
= (!getTexturePath(name
).empty());
365 m_source_image_existence
.set(name
, is_known
);
369 // Processes queued texture requests from other threads.
370 // Shall be called from the main thread.
373 // Insert an image into the cache without touching the filesystem.
374 // Shall be called from the main thread.
375 void insertSourceImage(const std::string
&name
, video::IImage
*img
);
377 // Rebuild images and textures from the current set of source images
378 // Shall be called from the main thread.
379 void rebuildImagesAndTextures();
381 video::ITexture
* getNormalTexture(const std::string
&name
);
382 video::SColor
getTextureAverageColor(const std::string
&name
);
383 video::ITexture
*getShaderFlagsTexture(bool normamap_present
);
387 // The id of the thread that is allowed to use irrlicht directly
388 std::thread::id m_main_thread
;
390 // Cache of source images
391 // This should be only accessed from the main thread
392 SourceImageCache m_sourcecache
;
394 // Generate a texture
395 u32
generateTexture(const std::string
&name
);
397 // Generate image based on a string like "stone.png" or "[crack:1:0".
398 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
399 bool generateImagePart(std::string part_of_name
, video::IImage
*& baseimg
);
401 /*! Generates an image from a full string like
402 * "stone.png^mineral_coal.png^[crack:1:0".
403 * Shall be called from the main thread.
404 * The returned Image should be dropped.
406 video::IImage
* generateImage(const std::string
&name
);
408 // Thread-safe cache of what source images are known (true = known)
409 MutexedMap
<std::string
, bool> m_source_image_existence
;
411 // A texture id is index in this array.
412 // The first position contains a NULL texture.
413 std::vector
<TextureInfo
> m_textureinfo_cache
;
414 // Maps a texture name to an index in the former.
415 std::map
<std::string
, u32
> m_name_to_id
;
416 // The two former containers are behind this mutex
417 std::mutex m_textureinfo_cache_mutex
;
419 // Queued texture fetches (to be processed by the main thread)
420 RequestQueue
<std::string
, u32
, u8
, u8
> m_get_texture_queue
;
422 // Textures that have been overwritten with other ones
423 // but can't be deleted because the ITexture* might still be used
424 std::vector
<video::ITexture
*> m_texture_trash
;
426 // Maps image file names to loaded palettes.
427 std::unordered_map
<std::string
, Palette
> m_palettes
;
429 // Cached settings needed for making textures from meshes
430 bool m_setting_trilinear_filter
;
431 bool m_setting_bilinear_filter
;
432 bool m_setting_anisotropic_filter
;
435 IWritableTextureSource
*createTextureSource()
437 return new TextureSource();
440 TextureSource::TextureSource()
442 m_main_thread
= std::this_thread::get_id();
444 // Add a NULL TextureInfo as the first index, named ""
445 m_textureinfo_cache
.emplace_back("");
446 m_name_to_id
[""] = 0;
448 // Cache some settings
449 // Note: Since this is only done once, the game must be restarted
450 // for these settings to take effect
451 m_setting_trilinear_filter
= g_settings
->getBool("trilinear_filter");
452 m_setting_bilinear_filter
= g_settings
->getBool("bilinear_filter");
453 m_setting_anisotropic_filter
= g_settings
->getBool("anisotropic_filter");
456 TextureSource::~TextureSource()
458 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
460 unsigned int textures_before
= driver
->getTextureCount();
462 for (const auto &iter
: m_textureinfo_cache
) {
465 driver
->removeTexture(iter
.texture
);
467 m_textureinfo_cache
.clear();
469 for (auto t
: m_texture_trash
) {
470 //cleanup trashed texture
471 driver
->removeTexture(t
);
474 infostream
<< "~TextureSource() before cleanup: "<< textures_before
475 << " after: " << driver
->getTextureCount() << std::endl
;
478 u32
TextureSource::getTextureId(const std::string
&name
)
480 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
484 See if texture already exists
486 MutexAutoLock
lock(m_textureinfo_cache_mutex
);
487 std::map
<std::string
, u32
>::iterator n
;
488 n
= m_name_to_id
.find(name
);
489 if (n
!= m_name_to_id
.end())
498 if (std::this_thread::get_id() == m_main_thread
) {
499 return generateTexture(name
);
503 infostream
<<"getTextureId(): Queued: name=\""<<name
<<"\""<<std::endl
;
505 // We're gonna ask the result to be put into here
506 static ResultQueue
<std::string
, u32
, u8
, u8
> result_queue
;
508 // Throw a request in
509 m_get_texture_queue
.add(name
, 0, 0, &result_queue
);
513 // Wait result for a second
514 GetResult
<std::string
, u32
, u8
, u8
>
515 result
= result_queue
.pop_front(1000);
517 if (result
.key
== name
) {
521 } catch(ItemNotFoundException
&e
) {
522 errorstream
<< "Waiting for texture " << name
<< " timed out." << std::endl
;
526 infostream
<< "getTextureId(): Failed" << std::endl
;
531 // Draw an image on top of an another one, using the alpha channel of the
533 static void blit_with_alpha(video::IImage
*src
, video::IImage
*dst
,
534 v2s32 src_pos
, v2s32 dst_pos
, v2u32 size
);
536 // Like blit_with_alpha, but only modifies destination pixels that
538 static void blit_with_alpha_overlay(video::IImage
*src
, video::IImage
*dst
,
539 v2s32 src_pos
, v2s32 dst_pos
, v2u32 size
);
541 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
542 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
543 // color alpha with the destination alpha.
544 // Otherwise, any pixels that are not fully transparent get the color alpha.
545 static void apply_colorize(video::IImage
*dst
, v2u32 dst_pos
, v2u32 size
,
546 const video::SColor
&color
, int ratio
, bool keep_alpha
);
548 // paint a texture using the given color
549 static void apply_multiplication(video::IImage
*dst
, v2u32 dst_pos
, v2u32 size
,
550 const video::SColor
&color
);
552 // Apply a mask to an image
553 static void apply_mask(video::IImage
*mask
, video::IImage
*dst
,
554 v2s32 mask_pos
, v2s32 dst_pos
, v2u32 size
);
556 // Draw or overlay a crack
557 static void draw_crack(video::IImage
*crack
, video::IImage
*dst
,
558 bool use_overlay
, s32 frame_count
, s32 progression
,
559 video::IVideoDriver
*driver
, u8 tiles
= 1);
562 void brighten(video::IImage
*image
);
563 // Parse a transform name
564 u32
parseImageTransform(const std::string
& s
);
565 // Apply transform to image dimension
566 core::dimension2d
<u32
> imageTransformDimension(u32 transform
, core::dimension2d
<u32
> dim
);
567 // Apply transform to image data
568 void imageTransform(u32 transform
, video::IImage
*src
, video::IImage
*dst
);
571 This method generates all the textures
573 u32
TextureSource::generateTexture(const std::string
&name
)
575 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
577 // Empty name means texture 0
579 infostream
<<"generateTexture(): name is empty"<<std::endl
;
585 See if texture already exists
587 MutexAutoLock
lock(m_textureinfo_cache_mutex
);
588 std::map
<std::string
, u32
>::iterator n
;
589 n
= m_name_to_id
.find(name
);
590 if (n
!= m_name_to_id
.end()) {
596 Calling only allowed from main thread
598 if (std::this_thread::get_id() != m_main_thread
) {
599 errorstream
<<"TextureSource::generateTexture() "
600 "called not from main thread"<<std::endl
;
604 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
605 sanity_check(driver
);
607 video::IImage
*img
= generateImage(name
);
609 video::ITexture
*tex
= NULL
;
613 img
= Align2Npot2(img
, driver
);
615 // Create texture from resulting image
616 tex
= driver
->addTexture(name
.c_str(), img
);
617 guiScalingCache(io::path(name
.c_str()), driver
, img
);
622 Add texture to caches (add NULL textures too)
625 MutexAutoLock
lock(m_textureinfo_cache_mutex
);
627 u32 id
= m_textureinfo_cache
.size();
628 TextureInfo
ti(name
, tex
);
629 m_textureinfo_cache
.push_back(ti
);
630 m_name_to_id
[name
] = id
;
635 std::string
TextureSource::getTextureName(u32 id
)
637 MutexAutoLock
lock(m_textureinfo_cache_mutex
);
639 if (id
>= m_textureinfo_cache
.size())
641 errorstream
<<"TextureSource::getTextureName(): id="<<id
642 <<" >= m_textureinfo_cache.size()="
643 <<m_textureinfo_cache
.size()<<std::endl
;
647 return m_textureinfo_cache
[id
].name
;
650 video::ITexture
* TextureSource::getTexture(u32 id
)
652 MutexAutoLock
lock(m_textureinfo_cache_mutex
);
654 if (id
>= m_textureinfo_cache
.size())
657 return m_textureinfo_cache
[id
].texture
;
660 video::ITexture
* TextureSource::getTexture(const std::string
&name
, u32
*id
)
662 u32 actual_id
= getTextureId(name
);
666 return getTexture(actual_id
);
669 video::ITexture
* TextureSource::getTextureForMesh(const std::string
&name
, u32
*id
)
671 static thread_local
bool filter_needed
=
672 g_settings
->getBool("texture_clean_transparent") ||
673 ((m_setting_trilinear_filter
|| m_setting_bilinear_filter
) &&
674 g_settings
->getS32("texture_min_size") > 1);
675 // Avoid duplicating texture if it won't actually change
677 return getTexture(name
+ "^[applyfiltersformesh", id
);
678 return getTexture(name
, id
);
681 Palette
* TextureSource::getPalette(const std::string
&name
)
683 // Only the main thread may load images
684 sanity_check(std::this_thread::get_id() == m_main_thread
);
689 auto it
= m_palettes
.find(name
);
690 if (it
== m_palettes
.end()) {
692 video::IImage
*img
= generateImage(name
);
694 warningstream
<< "TextureSource::getPalette(): palette \"" << name
695 << "\" could not be loaded." << std::endl
;
699 u32 w
= img
->getDimension().Width
;
700 u32 h
= img
->getDimension().Height
;
701 // Real area of the image
706 warningstream
<< "TextureSource::getPalette(): the specified"
707 << " palette image \"" << name
<< "\" is larger than 256"
708 << " pixels, using the first 256." << std::endl
;
710 } else if (256 % area
!= 0)
711 warningstream
<< "TextureSource::getPalette(): the "
712 << "specified palette image \"" << name
<< "\" does not "
713 << "contain power of two pixels." << std::endl
;
714 // We stretch the palette so it will fit 256 values
715 // This many param2 values will have the same color
716 u32 step
= 256 / area
;
717 // For each pixel in the image
718 for (u32 i
= 0; i
< area
; i
++) {
719 video::SColor c
= img
->getPixel(i
% w
, i
/ w
);
720 // Fill in palette with 'step' colors
721 for (u32 j
= 0; j
< step
; j
++)
722 new_palette
.push_back(c
);
725 // Fill in remaining elements
726 while (new_palette
.size() < 256)
727 new_palette
.emplace_back(0xFFFFFFFF);
728 m_palettes
[name
] = new_palette
;
729 it
= m_palettes
.find(name
);
731 if (it
!= m_palettes
.end())
732 return &((*it
).second
);
736 void TextureSource::processQueue()
741 //NOTE this is only thread safe for ONE consumer thread!
742 if (!m_get_texture_queue
.empty())
744 GetRequest
<std::string
, u32
, u8
, u8
>
745 request
= m_get_texture_queue
.pop();
747 /*infostream<<"TextureSource::processQueue(): "
748 <<"got texture request with "
749 <<"name=\""<<request.key<<"\""
752 m_get_texture_queue
.pushResult(request
, generateTexture(request
.key
));
756 void TextureSource::insertSourceImage(const std::string
&name
, video::IImage
*img
)
758 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
760 sanity_check(std::this_thread::get_id() == m_main_thread
);
762 m_sourcecache
.insert(name
, img
, true);
763 m_source_image_existence
.set(name
, true);
766 void TextureSource::rebuildImagesAndTextures()
768 MutexAutoLock
lock(m_textureinfo_cache_mutex
);
770 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
771 sanity_check(driver
);
773 infostream
<< "TextureSource: recreating " << m_textureinfo_cache
.size()
774 << " textures" << std::endl
;
777 for (TextureInfo
&ti
: m_textureinfo_cache
) {
778 video::IImage
*img
= generateImage(ti
.name
);
780 img
= Align2Npot2(img
, driver
);
782 // Create texture from resulting image
783 video::ITexture
*t
= NULL
;
785 t
= driver
->addTexture(ti
.name
.c_str(), img
);
786 guiScalingCache(io::path(ti
.name
.c_str()), driver
, img
);
789 video::ITexture
*t_old
= ti
.texture
;
794 m_texture_trash
.push_back(t_old
);
798 inline static void applyShadeFactor(video::SColor
&color
, u32 factor
)
800 u32 f
= core::clamp
<u32
>(factor
, 0, 256);
801 color
.setRed(color
.getRed() * f
/ 256);
802 color
.setGreen(color
.getGreen() * f
/ 256);
803 color
.setBlue(color
.getBlue() * f
/ 256);
806 static video::IImage
*createInventoryCubeImage(
807 video::IImage
*top
, video::IImage
*left
, video::IImage
*right
)
809 core::dimension2du size_top
= top
->getDimension();
810 core::dimension2du size_left
= left
->getDimension();
811 core::dimension2du size_right
= right
->getDimension();
813 u32 size
= npot2(std::max({
814 size_top
.Width
, size_top
.Height
,
815 size_left
.Width
, size_left
.Height
,
816 size_right
.Width
, size_right
.Height
,
819 // It must be divisible by 4, to let everything work correctly.
820 // But it is a power of 2, so being at least 4 is the same.
821 // And the resulting texture should't be too large as well.
822 size
= core::clamp
<u32
>(size
, 4, 64);
824 // With such parameters, the cube fits exactly, touching each image line
825 // from `0` to `cube_size - 1`. (Note that division is exact here).
826 u32 cube_size
= 9 * size
;
827 u32 offset
= size
/ 2;
829 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
831 auto lock_image
= [size
, driver
] (video::IImage
*&image
) -> const u32
* {
833 core::dimension2du dim
= image
->getDimension();
834 video::ECOLOR_FORMAT format
= image
->getColorFormat();
835 if (dim
.Width
!= size
|| dim
.Height
!= size
|| format
!= video::ECF_A8R8G8B8
) {
836 video::IImage
*scaled
= driver
->createImage(video::ECF_A8R8G8B8
, {size
, size
});
837 image
->copyToScaling(scaled
);
841 sanity_check(image
->getPitch() == 4 * size
);
842 return reinterpret_cast<u32
*>(image
->lock());
844 auto free_image
= [] (video::IImage
*image
) -> void {
849 video::IImage
*result
= driver
->createImage(video::ECF_A8R8G8B8
, {cube_size
, cube_size
});
850 sanity_check(result
->getPitch() == 4 * cube_size
);
851 result
->fill(video::SColor(0x00000000u
));
852 u32
*target
= reinterpret_cast<u32
*>(result
->lock());
854 // Draws single cube face
855 // `shade_factor` is face brightness, in range [0.0, 1.0]
856 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
857 // `offsets` list pixels to be drawn for single source pixel
858 auto draw_image
= [=] (video::IImage
*image
, float shade_factor
,
859 s16 xu
, s16 xv
, s16 x1
,
860 s16 yu
, s16 yv
, s16 y1
,
861 std::initializer_list
<v2s16
> offsets
) -> void {
862 u32 brightness
= core::clamp
<u32
>(256 * shade_factor
, 0, 256);
863 const u32
*source
= lock_image(image
);
864 for (u16 v
= 0; v
< size
; v
++) {
865 for (u16 u
= 0; u
< size
; u
++) {
866 video::SColor
pixel(*source
);
867 applyShadeFactor(pixel
, brightness
);
868 s16 x
= xu
* u
+ xv
* v
+ x1
;
869 s16 y
= yu
* u
+ yv
* v
+ y1
;
870 for (const auto &off
: offsets
)
871 target
[(y
+ off
.Y
) * cube_size
+ (x
+ off
.X
) + offset
] = pixel
.color
;
878 draw_image(top
, 1.000000f
,
879 4, -4, 4 * (size
- 1),
882 {2, 0}, {3, 0}, {4, 0}, {5, 0},
883 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
884 {2, 2}, {3, 2}, {4, 2}, {5, 2},
887 draw_image(left
, 0.836660f
,
892 {0, 1}, {1, 1}, {2, 1}, {3, 1},
893 {0, 2}, {1, 2}, {2, 2}, {3, 2},
894 {0, 3}, {1, 3}, {2, 3}, {3, 3},
895 {0, 4}, {1, 4}, {2, 4}, {3, 4},
899 draw_image(right
, 0.670820f
,
904 {0, 1}, {1, 1}, {2, 1}, {3, 1},
905 {0, 2}, {1, 2}, {2, 2}, {3, 2},
906 {0, 3}, {1, 3}, {2, 3}, {3, 3},
907 {0, 4}, {1, 4}, {2, 4}, {3, 4},
915 video::IImage
* TextureSource::generateImage(const std::string
&name
)
917 // Get the base image
919 const char separator
= '^';
920 const char escape
= '\\';
921 const char paren_open
= '(';
922 const char paren_close
= ')';
924 // Find last separator in the name
925 s32 last_separator_pos
= -1;
927 for (s32 i
= name
.size() - 1; i
>= 0; i
--) {
928 if (i
> 0 && name
[i
-1] == escape
)
932 if (paren_bal
== 0) {
933 last_separator_pos
= i
;
934 i
= -1; // break out of loop
938 if (paren_bal
== 0) {
939 errorstream
<< "generateImage(): unbalanced parentheses"
940 << "(extranous '(') while generating texture \""
941 << name
<< "\"" << std::endl
;
954 errorstream
<< "generateImage(): unbalanced parentheses"
955 << "(missing matching '(') while generating texture \""
956 << name
<< "\"" << std::endl
;
961 video::IImage
*baseimg
= NULL
;
964 If separator was found, make the base image
965 using a recursive call.
967 if (last_separator_pos
!= -1) {
968 baseimg
= generateImage(name
.substr(0, last_separator_pos
));
972 Parse out the last part of the name of the image and act
976 std::string last_part_of_name
= name
.substr(last_separator_pos
+ 1);
979 If this name is enclosed in parentheses, generate it
980 and blit it onto the base image
982 if (last_part_of_name
[0] == paren_open
983 && last_part_of_name
[last_part_of_name
.size() - 1] == paren_close
) {
984 std::string name2
= last_part_of_name
.substr(1,
985 last_part_of_name
.size() - 2);
986 video::IImage
*tmp
= generateImage(name2
);
988 errorstream
<< "generateImage(): "
989 "Failed to generate \"" << name2
<< "\""
993 core::dimension2d
<u32
> dim
= tmp
->getDimension();
995 blit_with_alpha(tmp
, baseimg
, v2s32(0, 0), v2s32(0, 0), dim
);
1000 } else if (!generateImagePart(last_part_of_name
, baseimg
)) {
1001 // Generate image according to part of name
1002 errorstream
<< "generateImage(): "
1003 "Failed to generate \"" << last_part_of_name
<< "\""
1007 // If no resulting image, print a warning
1008 if (baseimg
== NULL
) {
1009 errorstream
<< "generateImage(): baseimg is NULL (attempted to"
1010 " create texture \"" << name
<< "\")" << std::endl
;
1019 static inline u16
get_GL_major_version()
1021 const GLubyte
*gl_version
= glGetString(GL_VERSION
);
1022 return (u16
) (gl_version
[0] - '0');
1026 * Check if hardware requires npot2 aligned textures
1027 * @return true if alignment NOT(!) requires, false otherwise
1030 bool hasNPotSupport()
1032 // Only GLES2 is trusted to correctly report npot support
1033 // Note: we cache the boolean result, the GL context will never change.
1034 static const bool supported
= get_GL_major_version() > 1 &&
1035 glGetString(GL_EXTENSIONS
) &&
1036 strstr((char *)glGetString(GL_EXTENSIONS
), "GL_OES_texture_npot");
1041 * Check and align image to npot2 if required by hardware
1042 * @param image image to check for npot2 alignment
1043 * @param driver driver to use for image operations
1044 * @return image or copy of image aligned to npot2
1047 video::IImage
* Align2Npot2(video::IImage
* image
,
1048 video::IVideoDriver
* driver
)
1053 if (hasNPotSupport())
1056 core::dimension2d
<u32
> dim
= image
->getDimension();
1057 unsigned int height
= npot2(dim
.Height
);
1058 unsigned int width
= npot2(dim
.Width
);
1060 if (dim
.Height
== height
&& dim
.Width
== width
)
1063 if (dim
.Height
> height
)
1065 if (dim
.Width
> width
)
1068 video::IImage
*targetimage
=
1069 driver
->createImage(video::ECF_A8R8G8B8
,
1070 core::dimension2d
<u32
>(width
, height
));
1072 if (targetimage
!= NULL
)
1073 image
->copyToScaling(targetimage
);
1080 static std::string
unescape_string(const std::string
&str
, const char esc
= '\\')
1083 size_t pos
= 0, cpos
;
1084 out
.reserve(str
.size());
1086 cpos
= str
.find_first_of(esc
, pos
);
1087 if (cpos
== std::string::npos
) {
1088 out
+= str
.substr(pos
);
1091 out
+= str
.substr(pos
, cpos
- pos
) + str
[cpos
+ 1];
1097 bool TextureSource::generateImagePart(std::string part_of_name
,
1098 video::IImage
*& baseimg
)
1100 const char escape
= '\\'; // same as in generateImage()
1101 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
1102 sanity_check(driver
);
1104 // Stuff starting with [ are special commands
1105 if (part_of_name
.empty() || part_of_name
[0] != '[') {
1106 video::IImage
*image
= m_sourcecache
.getOrLoad(part_of_name
);
1108 image
= Align2Npot2(image
, driver
);
1110 if (image
== NULL
) {
1111 if (!part_of_name
.empty()) {
1113 // Do not create normalmap dummies
1114 if (part_of_name
.find("_normal.png") != std::string::npos
) {
1115 warningstream
<< "generateImage(): Could not load normal map \""
1116 << part_of_name
<< "\"" << std::endl
;
1120 errorstream
<< "generateImage(): Could not load image \""
1121 << part_of_name
<< "\" while building texture; "
1122 "Creating a dummy image" << std::endl
;
1125 // Just create a dummy image
1126 //core::dimension2d<u32> dim(2,2);
1127 core::dimension2d
<u32
> dim(1,1);
1128 image
= driver
->createImage(video::ECF_A8R8G8B8
, dim
);
1129 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 image
->setPixel(0,0, video::SColor(255,myrand()%256,
1135 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));*/
1144 // If base image is NULL, load as base.
1145 if (baseimg
== NULL
)
1147 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
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.
1153 core::dimension2d
<u32
> dim
= image
->getDimension();
1154 baseimg
= driver
->createImage(video::ECF_A8R8G8B8
, dim
);
1155 image
->copyTo(baseimg
);
1157 // Else blit on base.
1160 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1161 // Size of the copied area
1162 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 core::position2d
<s32
> pos_to(0,0);
1166 // Position to copy the blitted from in the blitted image
1167 core::position2d
<s32
> pos_from(0,0);
1169 /*image->copyToWithAlpha(baseimg, pos_to,
1170 core::rect<s32>(pos_from, dim),
1171 video::SColor(255,255,255,255),
1174 core::dimension2d
<u32
> dim_dst
= baseimg
->getDimension();
1175 if (dim
== dim_dst
) {
1176 blit_with_alpha(image
, baseimg
, pos_from
, pos_to
, dim
);
1177 } else if (dim
.Width
* dim
.Height
< dim_dst
.Width
* dim_dst
.Height
) {
1178 // Upscale overlying image
1179 video::IImage
*scaled_image
= RenderingEngine::get_video_driver()->
1180 createImage(video::ECF_A8R8G8B8
, dim_dst
);
1181 image
->copyToScaling(scaled_image
);
1183 blit_with_alpha(scaled_image
, baseimg
, pos_from
, pos_to
, dim_dst
);
1184 scaled_image
->drop();
1186 // Upscale base image
1187 video::IImage
*scaled_base
= RenderingEngine::get_video_driver()->
1188 createImage(video::ECF_A8R8G8B8
, dim
);
1189 baseimg
->copyToScaling(scaled_base
);
1191 baseimg
= scaled_base
;
1193 blit_with_alpha(image
, baseimg
, pos_from
, pos_to
, dim
);
1201 // A special texture modification
1203 /*infostream<<"generateImage(): generating special "
1204 <<"modification \""<<part_of_name<<"\""
1210 Adds a cracking texture
1211 N = animation frame count, P = crack progression
1213 if (str_starts_with(part_of_name
, "[crack"))
1215 if (baseimg
== NULL
) {
1216 errorstream
<<"generateImagePart(): baseimg == NULL "
1217 <<"for part_of_name=\""<<part_of_name
1218 <<"\", cancelling."<<std::endl
;
1222 // Crack image number and overlay option
1223 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1224 bool use_overlay
= (part_of_name
[6] == 'o');
1225 Strfnd
sf(part_of_name
);
1227 s32 frame_count
= stoi(sf
.next(":"));
1228 s32 progression
= stoi(sf
.next(":"));
1230 // Check whether there is the <tiles> argument, that is,
1231 // whether there are 3 arguments. If so, shift values
1232 // as the first and not the last argument is optional.
1233 auto s
= sf
.next(":");
1235 tiles
= frame_count
;
1236 frame_count
= progression
;
1237 progression
= stoi(s
);
1240 if (progression
>= 0) {
1244 It is an image with a number of cracking stages
1247 video::IImage
*img_crack
= m_sourcecache
.getOrLoad(
1248 "crack_anylength.png");
1251 draw_crack(img_crack
, baseimg
,
1252 use_overlay
, frame_count
,
1253 progression
, driver
, tiles
);
1259 [combine:WxH:X,Y=filename:X,Y=filename2
1260 Creates a bigger texture from any amount of smaller ones
1262 else if (str_starts_with(part_of_name
, "[combine"))
1264 Strfnd
sf(part_of_name
);
1266 u32 w0
= stoi(sf
.next("x"));
1267 u32 h0
= stoi(sf
.next(":"));
1268 core::dimension2d
<u32
> dim(w0
,h0
);
1269 if (baseimg
== NULL
) {
1270 baseimg
= driver
->createImage(video::ECF_A8R8G8B8
, dim
);
1271 baseimg
->fill(video::SColor(0,0,0,0));
1273 while (!sf
.at_end()) {
1274 u32 x
= stoi(sf
.next(","));
1275 u32 y
= stoi(sf
.next("="));
1276 std::string filename
= unescape_string(sf
.next_esc(":", escape
), escape
);
1277 infostream
<<"Adding \""<<filename
1278 <<"\" to combined ("<<x
<<","<<y
<<")"
1280 video::IImage
*img
= generateImage(filename
);
1282 core::dimension2d
<u32
> dim
= img
->getDimension();
1283 core::position2d
<s32
> pos_base(x
, y
);
1284 video::IImage
*img2
=
1285 driver
->createImage(video::ECF_A8R8G8B8
, dim
);
1288 /*img2->copyToWithAlpha(baseimg, pos_base,
1289 core::rect<s32>(v2s32(0,0), dim),
1290 video::SColor(255,255,255,255),
1292 blit_with_alpha(img2
, baseimg
, v2s32(0,0), pos_base
, dim
);
1295 errorstream
<< "generateImagePart(): Failed to load image \""
1296 << filename
<< "\" for [combine" << std::endl
;
1303 else if (str_starts_with(part_of_name
, "[brighten"))
1305 if (baseimg
== NULL
) {
1306 errorstream
<<"generateImagePart(): baseimg==NULL "
1307 <<"for part_of_name=\""<<part_of_name
1308 <<"\", cancelling."<<std::endl
;
1316 Make image completely opaque.
1317 Used for the leaves texture when in old leaves mode, so
1318 that the transparent parts don't look completely black
1319 when simple alpha channel is used for rendering.
1321 else if (str_starts_with(part_of_name
, "[noalpha"))
1323 if (baseimg
== NULL
){
1324 errorstream
<<"generateImagePart(): baseimg==NULL "
1325 <<"for part_of_name=\""<<part_of_name
1326 <<"\", cancelling."<<std::endl
;
1330 core::dimension2d
<u32
> dim
= baseimg
->getDimension();
1332 // Set alpha to full
1333 for (u32 y
=0; y
<dim
.Height
; y
++)
1334 for (u32 x
=0; x
<dim
.Width
; x
++)
1336 video::SColor c
= baseimg
->getPixel(x
,y
);
1338 baseimg
->setPixel(x
,y
,c
);
1343 Convert one color to transparent.
1345 else if (str_starts_with(part_of_name
, "[makealpha:"))
1347 if (baseimg
== NULL
) {
1348 errorstream
<<"generateImagePart(): baseimg == NULL "
1349 <<"for part_of_name=\""<<part_of_name
1350 <<"\", cancelling."<<std::endl
;
1354 Strfnd
sf(part_of_name
.substr(11));
1355 u32 r1
= stoi(sf
.next(","));
1356 u32 g1
= stoi(sf
.next(","));
1357 u32 b1
= stoi(sf
.next(""));
1359 core::dimension2d
<u32
> dim
= baseimg
->getDimension();
1361 /*video::IImage *oldbaseimg = baseimg;
1362 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1363 oldbaseimg->copyTo(baseimg);
1364 oldbaseimg->drop();*/
1366 // Set alpha to full
1367 for (u32 y
=0; y
<dim
.Height
; y
++)
1368 for (u32 x
=0; x
<dim
.Width
; x
++)
1370 video::SColor c
= baseimg
->getPixel(x
,y
);
1372 u32 g
= c
.getGreen();
1373 u32 b
= c
.getBlue();
1374 if (!(r
== r1
&& g
== g1
&& b
== b1
))
1377 baseimg
->setPixel(x
,y
,c
);
1382 Rotates and/or flips the image.
1384 N can be a number (between 0 and 7) or a transform name.
1385 Rotations are counter-clockwise.
1387 1 R90 rotate by 90 degrees
1388 2 R180 rotate by 180 degrees
1389 3 R270 rotate by 270 degrees
1391 5 FXR90 flip X then rotate by 90 degrees
1393 7 FYR90 flip Y then rotate by 90 degrees
1395 Note: Transform names can be concatenated to produce
1396 their product (applies the first then the second).
1397 The resulting transform will be equivalent to one of the
1398 eight existing ones, though (see: dihedral group).
1400 else if (str_starts_with(part_of_name
, "[transform"))
1402 if (baseimg
== NULL
) {
1403 errorstream
<<"generateImagePart(): baseimg == NULL "
1404 <<"for part_of_name=\""<<part_of_name
1405 <<"\", cancelling."<<std::endl
;
1409 u32 transform
= parseImageTransform(part_of_name
.substr(10));
1410 core::dimension2d
<u32
> dim
= imageTransformDimension(
1411 transform
, baseimg
->getDimension());
1412 video::IImage
*image
= driver
->createImage(
1413 baseimg
->getColorFormat(), dim
);
1414 sanity_check(image
!= NULL
);
1415 imageTransform(transform
, baseimg
, image
);
1420 [inventorycube{topimage{leftimage{rightimage
1421 In every subimage, replace ^ with &.
1422 Create an "inventory cube".
1423 NOTE: This should be used only on its own.
1424 Example (a grass block (not actually used in game):
1425 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1427 else if (str_starts_with(part_of_name
, "[inventorycube"))
1429 if (baseimg
!= NULL
){
1430 errorstream
<<"generateImagePart(): baseimg != NULL "
1431 <<"for part_of_name=\""<<part_of_name
1432 <<"\", cancelling."<<std::endl
;
1436 str_replace(part_of_name
, '&', '^');
1437 Strfnd
sf(part_of_name
);
1439 std::string imagename_top
= sf
.next("{");
1440 std::string imagename_left
= sf
.next("{");
1441 std::string imagename_right
= sf
.next("{");
1443 // Generate images for the faces of the cube
1444 video::IImage
*img_top
= generateImage(imagename_top
);
1445 video::IImage
*img_left
= generateImage(imagename_left
);
1446 video::IImage
*img_right
= generateImage(imagename_right
);
1448 if (img_top
== NULL
|| img_left
== NULL
|| img_right
== NULL
) {
1449 errorstream
<< "generateImagePart(): Failed to create textures"
1450 << " for inventorycube \"" << part_of_name
<< "\""
1452 baseimg
= generateImage(imagename_top
);
1456 baseimg
= createInventoryCubeImage(img_top
, img_left
, img_right
);
1458 // Face images are not needed anymore
1466 [lowpart:percent:filename
1467 Adds the lower part of a texture
1469 else if (str_starts_with(part_of_name
, "[lowpart:"))
1471 Strfnd
sf(part_of_name
);
1473 u32 percent
= stoi(sf
.next(":"));
1474 std::string filename
= unescape_string(sf
.next_esc(":", escape
), escape
);
1476 if (baseimg
== NULL
)
1477 baseimg
= driver
->createImage(video::ECF_A8R8G8B8
, v2u32(16,16));
1478 video::IImage
*img
= generateImage(filename
);
1481 core::dimension2d
<u32
> dim
= img
->getDimension();
1482 core::position2d
<s32
> pos_base(0, 0);
1483 video::IImage
*img2
=
1484 driver
->createImage(video::ECF_A8R8G8B8
, dim
);
1487 core::position2d
<s32
> clippos(0, 0);
1488 clippos
.Y
= dim
.Height
* (100-percent
) / 100;
1489 core::dimension2d
<u32
> clipdim
= dim
;
1490 clipdim
.Height
= clipdim
.Height
* percent
/ 100 + 1;
1491 core::rect
<s32
> cliprect(clippos
, clipdim
);
1492 img2
->copyToWithAlpha(baseimg
, pos_base
,
1493 core::rect
<s32
>(v2s32(0,0), dim
),
1494 video::SColor(255,255,255,255),
1501 Crops a frame of a vertical animation.
1502 N = frame count, I = frame index
1504 else if (str_starts_with(part_of_name
, "[verticalframe:"))
1506 Strfnd
sf(part_of_name
);
1508 u32 frame_count
= stoi(sf
.next(":"));
1509 u32 frame_index
= stoi(sf
.next(":"));
1511 if (baseimg
== NULL
){
1512 errorstream
<<"generateImagePart(): baseimg != NULL "
1513 <<"for part_of_name=\""<<part_of_name
1514 <<"\", cancelling."<<std::endl
;
1518 v2u32 frame_size
= baseimg
->getDimension();
1519 frame_size
.Y
/= frame_count
;
1521 video::IImage
*img
= driver
->createImage(video::ECF_A8R8G8B8
,
1524 errorstream
<<"generateImagePart(): Could not create image "
1525 <<"for part_of_name=\""<<part_of_name
1526 <<"\", cancelling."<<std::endl
;
1530 // Fill target image with transparency
1531 img
->fill(video::SColor(0,0,0,0));
1533 core::dimension2d
<u32
> dim
= frame_size
;
1534 core::position2d
<s32
> pos_dst(0, 0);
1535 core::position2d
<s32
> pos_src(0, frame_index
* frame_size
.Y
);
1536 baseimg
->copyToWithAlpha(img
, pos_dst
,
1537 core::rect
<s32
>(pos_src
, dim
),
1538 video::SColor(255,255,255,255),
1546 Applies a mask to an image
1548 else if (str_starts_with(part_of_name
, "[mask:"))
1550 if (baseimg
== NULL
) {
1551 errorstream
<< "generateImage(): baseimg == NULL "
1552 << "for part_of_name=\"" << part_of_name
1553 << "\", cancelling." << std::endl
;
1556 Strfnd
sf(part_of_name
);
1558 std::string filename
= unescape_string(sf
.next_esc(":", escape
), escape
);
1560 video::IImage
*img
= generateImage(filename
);
1562 apply_mask(img
, baseimg
, v2s32(0, 0), v2s32(0, 0),
1563 img
->getDimension());
1566 errorstream
<< "generateImage(): Failed to load \""
1567 << filename
<< "\".";
1572 multiplys a given color to any pixel of an image
1573 color = color as ColorString
1575 else if (str_starts_with(part_of_name
, "[multiply:")) {
1576 Strfnd
sf(part_of_name
);
1578 std::string color_str
= sf
.next(":");
1580 if (baseimg
== NULL
) {
1581 errorstream
<< "generateImagePart(): baseimg != NULL "
1582 << "for part_of_name=\"" << part_of_name
1583 << "\", cancelling." << std::endl
;
1587 video::SColor color
;
1589 if (!parseColorString(color_str
, color
, false))
1592 apply_multiplication(baseimg
, v2u32(0, 0), baseimg
->getDimension(), color
);
1596 Overlays image with given color
1597 color = color as ColorString
1599 else if (str_starts_with(part_of_name
, "[colorize:"))
1601 Strfnd
sf(part_of_name
);
1603 std::string color_str
= sf
.next(":");
1604 std::string ratio_str
= sf
.next(":");
1606 if (baseimg
== NULL
) {
1607 errorstream
<< "generateImagePart(): baseimg != NULL "
1608 << "for part_of_name=\"" << part_of_name
1609 << "\", cancelling." << std::endl
;
1613 video::SColor color
;
1615 bool keep_alpha
= false;
1617 if (!parseColorString(color_str
, color
, false))
1620 if (is_number(ratio_str
))
1621 ratio
= mystoi(ratio_str
, 0, 255);
1622 else if (ratio_str
== "alpha")
1625 apply_colorize(baseimg
, v2u32(0, 0), baseimg
->getDimension(), color
, ratio
, keep_alpha
);
1628 [applyfiltersformesh
1631 else if (str_starts_with(part_of_name
, "[applyfiltersformesh"))
1633 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1636 // Apply the "clean transparent" filter, if configured.
1637 if (g_settings
->getBool("texture_clean_transparent"))
1638 imageCleanTransparent(baseimg
, 127);
1640 /* Upscale textures to user's requested minimum size. This is a trick to make
1641 * filters look as good on low-res textures as on high-res ones, by making
1642 * low-res textures BECOME high-res ones. This is helpful for worlds that
1643 * mix high- and low-res textures, or for mods with least-common-denominator
1644 * textures that don't have the resources to offer high-res alternatives.
1646 const bool filter
= m_setting_trilinear_filter
|| m_setting_bilinear_filter
;
1647 const s32 scaleto
= filter
? g_settings
->getS32("texture_min_size") : 1;
1649 const core::dimension2d
<u32
> dim
= baseimg
->getDimension();
1651 /* Calculate scaling needed to make the shortest texture dimension
1652 * equal to the target minimum. If e.g. this is a vertical frames
1653 * animation, the short dimension will be the real size.
1655 if ((dim
.Width
== 0) || (dim
.Height
== 0)) {
1656 errorstream
<< "generateImagePart(): Illegal 0 dimension "
1657 << "for part_of_name=\""<< part_of_name
1658 << "\", cancelling." << std::endl
;
1661 u32 xscale
= scaleto
/ dim
.Width
;
1662 u32 yscale
= scaleto
/ dim
.Height
;
1663 u32 scale
= (xscale
> yscale
) ? xscale
: yscale
;
1665 // Never downscale; only scale up by 2x or more.
1667 u32 w
= scale
* dim
.Width
;
1668 u32 h
= scale
* dim
.Height
;
1669 const core::dimension2d
<u32
> newdim
= core::dimension2d
<u32
>(w
, h
);
1670 video::IImage
*newimg
= driver
->createImage(
1671 baseimg
->getColorFormat(), newdim
);
1672 baseimg
->copyToScaling(newimg
);
1680 Resizes the base image to the given dimensions
1682 else if (str_starts_with(part_of_name
, "[resize"))
1684 if (baseimg
== NULL
) {
1685 errorstream
<< "generateImagePart(): baseimg == NULL "
1686 << "for part_of_name=\""<< part_of_name
1687 << "\", cancelling." << std::endl
;
1691 Strfnd
sf(part_of_name
);
1693 u32 width
= stoi(sf
.next("x"));
1694 u32 height
= stoi(sf
.next(""));
1695 core::dimension2d
<u32
> dim(width
, height
);
1697 video::IImage
*image
= RenderingEngine::get_video_driver()->
1698 createImage(video::ECF_A8R8G8B8
, dim
);
1699 baseimg
->copyToScaling(image
);
1705 Makes the base image transparent according to the given ratio.
1706 R must be between 0 and 255.
1707 0 means totally transparent.
1708 255 means totally opaque.
1710 else if (str_starts_with(part_of_name
, "[opacity:")) {
1711 if (baseimg
== NULL
) {
1712 errorstream
<< "generateImagePart(): baseimg == NULL "
1713 << "for part_of_name=\"" << part_of_name
1714 << "\", cancelling." << std::endl
;
1718 Strfnd
sf(part_of_name
);
1721 u32 ratio
= mystoi(sf
.next(""), 0, 255);
1723 core::dimension2d
<u32
> dim
= baseimg
->getDimension();
1725 for (u32 y
= 0; y
< dim
.Height
; y
++)
1726 for (u32 x
= 0; x
< dim
.Width
; x
++)
1728 video::SColor c
= baseimg
->getPixel(x
, y
);
1729 c
.setAlpha(floor((c
.getAlpha() * ratio
) / 255 + 0.5));
1730 baseimg
->setPixel(x
, y
, c
);
1735 Inverts the given channels of the base image.
1736 Mode may contain the characters "r", "g", "b", "a".
1737 Only the channels that are mentioned in the mode string
1740 else if (str_starts_with(part_of_name
, "[invert:")) {
1741 if (baseimg
== NULL
) {
1742 errorstream
<< "generateImagePart(): baseimg == NULL "
1743 << "for part_of_name=\"" << part_of_name
1744 << "\", cancelling." << std::endl
;
1748 Strfnd
sf(part_of_name
);
1751 std::string mode
= sf
.next("");
1753 if (mode
.find('a') != std::string::npos
)
1754 mask
|= 0xff000000UL
;
1755 if (mode
.find('r') != std::string::npos
)
1756 mask
|= 0x00ff0000UL
;
1757 if (mode
.find('g') != std::string::npos
)
1758 mask
|= 0x0000ff00UL
;
1759 if (mode
.find('b') != std::string::npos
)
1760 mask
|= 0x000000ffUL
;
1762 core::dimension2d
<u32
> dim
= baseimg
->getDimension();
1764 for (u32 y
= 0; y
< dim
.Height
; y
++)
1765 for (u32 x
= 0; x
< dim
.Width
; x
++)
1767 video::SColor c
= baseimg
->getPixel(x
, y
);
1769 baseimg
->setPixel(x
, y
, c
);
1774 Retrieves a tile at position X,Y (in tiles)
1775 from the base image it assumes to be a
1776 tilesheet with dimensions W,H (in tiles).
1778 else if (part_of_name
.substr(0,7) == "[sheet:") {
1779 if (baseimg
== NULL
) {
1780 errorstream
<< "generateImagePart(): baseimg != NULL "
1781 << "for part_of_name=\"" << part_of_name
1782 << "\", cancelling." << std::endl
;
1786 Strfnd
sf(part_of_name
);
1788 u32 w0
= stoi(sf
.next("x"));
1789 u32 h0
= stoi(sf
.next(":"));
1790 u32 x0
= stoi(sf
.next(","));
1791 u32 y0
= stoi(sf
.next(":"));
1793 core::dimension2d
<u32
> img_dim
= baseimg
->getDimension();
1794 core::dimension2d
<u32
> tile_dim(v2u32(img_dim
) / v2u32(w0
, h0
));
1796 video::IImage
*img
= driver
->createImage(
1797 video::ECF_A8R8G8B8
, tile_dim
);
1799 errorstream
<< "generateImagePart(): Could not create image "
1800 << "for part_of_name=\"" << part_of_name
1801 << "\", cancelling." << std::endl
;
1805 img
->fill(video::SColor(0,0,0,0));
1806 v2u32
vdim(tile_dim
);
1807 core::rect
<s32
> rect(v2s32(x0
* vdim
.X
, y0
* vdim
.Y
), tile_dim
);
1808 baseimg
->copyToWithAlpha(img
, v2s32(0), rect
,
1809 video::SColor(255,255,255,255), NULL
);
1817 errorstream
<< "generateImagePart(): Invalid "
1818 " modification: \"" << part_of_name
<< "\"" << std::endl
;
1826 Calculate the color of a single pixel drawn on top of another pixel.
1828 This is a little more complicated than just video::SColor::getInterpolated
1829 because getInterpolated does not handle alpha correctly. For example, a
1830 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1831 pixel with alpha=160, while getInterpolated would yield alpha=96.
1833 static inline video::SColor
blitPixel(const video::SColor
&src_c
, const video::SColor
&dst_c
, u32 ratio
)
1835 if (dst_c
.getAlpha() == 0)
1837 video::SColor out_c
= src_c
.getInterpolated(dst_c
, (float)ratio
/ 255.0f
);
1838 out_c
.setAlpha(dst_c
.getAlpha() + (255 - dst_c
.getAlpha()) *
1839 src_c
.getAlpha() * ratio
/ (255 * 255));
1844 Draw an image on top of an another one, using the alpha channel of the
1847 This exists because IImage::copyToWithAlpha() doesn't seem to always
1850 static void blit_with_alpha(video::IImage
*src
, video::IImage
*dst
,
1851 v2s32 src_pos
, v2s32 dst_pos
, v2u32 size
)
1853 for (u32 y0
=0; y0
<size
.Y
; y0
++)
1854 for (u32 x0
=0; x0
<size
.X
; x0
++)
1856 s32 src_x
= src_pos
.X
+ x0
;
1857 s32 src_y
= src_pos
.Y
+ y0
;
1858 s32 dst_x
= dst_pos
.X
+ x0
;
1859 s32 dst_y
= dst_pos
.Y
+ y0
;
1860 video::SColor src_c
= src
->getPixel(src_x
, src_y
);
1861 video::SColor dst_c
= dst
->getPixel(dst_x
, dst_y
);
1862 dst_c
= blitPixel(src_c
, dst_c
, src_c
.getAlpha());
1863 dst
->setPixel(dst_x
, dst_y
, dst_c
);
1868 Draw an image on top of an another one, using the alpha channel of the
1869 source image; only modify fully opaque pixels in destinaion
1871 static void blit_with_alpha_overlay(video::IImage
*src
, video::IImage
*dst
,
1872 v2s32 src_pos
, v2s32 dst_pos
, v2u32 size
)
1874 for (u32 y0
=0; y0
<size
.Y
; y0
++)
1875 for (u32 x0
=0; x0
<size
.X
; x0
++)
1877 s32 src_x
= src_pos
.X
+ x0
;
1878 s32 src_y
= src_pos
.Y
+ y0
;
1879 s32 dst_x
= dst_pos
.X
+ x0
;
1880 s32 dst_y
= dst_pos
.Y
+ y0
;
1881 video::SColor src_c
= src
->getPixel(src_x
, src_y
);
1882 video::SColor dst_c
= dst
->getPixel(dst_x
, dst_y
);
1883 if (dst_c
.getAlpha() == 255 && src_c
.getAlpha() != 0)
1885 dst_c
= blitPixel(src_c
, dst_c
, src_c
.getAlpha());
1886 dst
->setPixel(dst_x
, dst_y
, dst_c
);
1891 // This function has been disabled because it is currently unused.
1892 // Feel free to re-enable if you find it handy.
1895 Draw an image on top of an another one, using the specified ratio
1896 modify all partially-opaque pixels in the destination.
1898 static void blit_with_interpolate_overlay(video::IImage
*src
, video::IImage
*dst
,
1899 v2s32 src_pos
, v2s32 dst_pos
, v2u32 size
, int ratio
)
1901 for (u32 y0
= 0; y0
< size
.Y
; y0
++)
1902 for (u32 x0
= 0; x0
< size
.X
; x0
++)
1904 s32 src_x
= src_pos
.X
+ x0
;
1905 s32 src_y
= src_pos
.Y
+ y0
;
1906 s32 dst_x
= dst_pos
.X
+ x0
;
1907 s32 dst_y
= dst_pos
.Y
+ y0
;
1908 video::SColor src_c
= src
->getPixel(src_x
, src_y
);
1909 video::SColor dst_c
= dst
->getPixel(dst_x
, dst_y
);
1910 if (dst_c
.getAlpha() > 0 && src_c
.getAlpha() != 0)
1913 dst_c
= src_c
.getInterpolated(dst_c
, (float)src_c
.getAlpha()/255.0f
);
1915 dst_c
= src_c
.getInterpolated(dst_c
, (float)ratio
/255.0f
);
1916 dst
->setPixel(dst_x
, dst_y
, dst_c
);
1923 Apply color to destination
1925 static void apply_colorize(video::IImage
*dst
, v2u32 dst_pos
, v2u32 size
,
1926 const video::SColor
&color
, int ratio
, bool keep_alpha
)
1928 u32 alpha
= color
.getAlpha();
1929 video::SColor dst_c
;
1930 if ((ratio
== -1 && alpha
== 255) || ratio
== 255) { // full replacement of color
1931 if (keep_alpha
) { // replace the color with alpha = dest alpha * color alpha
1933 for (u32 y
= dst_pos
.Y
; y
< dst_pos
.Y
+ size
.Y
; y
++)
1934 for (u32 x
= dst_pos
.X
; x
< dst_pos
.X
+ size
.X
; x
++) {
1935 u32 dst_alpha
= dst
->getPixel(x
, y
).getAlpha();
1936 if (dst_alpha
> 0) {
1937 dst_c
.setAlpha(dst_alpha
* alpha
/ 255);
1938 dst
->setPixel(x
, y
, dst_c
);
1941 } else { // replace the color including the alpha
1942 for (u32 y
= dst_pos
.Y
; y
< dst_pos
.Y
+ size
.Y
; y
++)
1943 for (u32 x
= dst_pos
.X
; x
< dst_pos
.X
+ size
.X
; x
++)
1944 if (dst
->getPixel(x
, y
).getAlpha() > 0)
1945 dst
->setPixel(x
, y
, color
);
1947 } else { // interpolate between the color and destination
1948 float interp
= (ratio
== -1 ? color
.getAlpha() / 255.0f
: ratio
/ 255.0f
);
1949 for (u32 y
= dst_pos
.Y
; y
< dst_pos
.Y
+ size
.Y
; y
++)
1950 for (u32 x
= dst_pos
.X
; x
< dst_pos
.X
+ size
.X
; x
++) {
1951 dst_c
= dst
->getPixel(x
, y
);
1952 if (dst_c
.getAlpha() > 0) {
1953 dst_c
= color
.getInterpolated(dst_c
, interp
);
1954 dst
->setPixel(x
, y
, dst_c
);
1961 Apply color to destination
1963 static void apply_multiplication(video::IImage
*dst
, v2u32 dst_pos
, v2u32 size
,
1964 const video::SColor
&color
)
1966 video::SColor dst_c
;
1968 for (u32 y
= dst_pos
.Y
; y
< dst_pos
.Y
+ size
.Y
; y
++)
1969 for (u32 x
= dst_pos
.X
; x
< dst_pos
.X
+ size
.X
; x
++) {
1970 dst_c
= dst
->getPixel(x
, y
);
1973 (dst_c
.getRed() * color
.getRed()) / 255,
1974 (dst_c
.getGreen() * color
.getGreen()) / 255,
1975 (dst_c
.getBlue() * color
.getBlue()) / 255
1977 dst
->setPixel(x
, y
, dst_c
);
1982 Apply mask to destination
1984 static void apply_mask(video::IImage
*mask
, video::IImage
*dst
,
1985 v2s32 mask_pos
, v2s32 dst_pos
, v2u32 size
)
1987 for (u32 y0
= 0; y0
< size
.Y
; y0
++) {
1988 for (u32 x0
= 0; x0
< size
.X
; x0
++) {
1989 s32 mask_x
= x0
+ mask_pos
.X
;
1990 s32 mask_y
= y0
+ mask_pos
.Y
;
1991 s32 dst_x
= x0
+ dst_pos
.X
;
1992 s32 dst_y
= y0
+ dst_pos
.Y
;
1993 video::SColor mask_c
= mask
->getPixel(mask_x
, mask_y
);
1994 video::SColor dst_c
= dst
->getPixel(dst_x
, dst_y
);
1995 dst_c
.color
&= mask_c
.color
;
1996 dst
->setPixel(dst_x
, dst_y
, dst_c
);
2001 video::IImage
*create_crack_image(video::IImage
*crack
, s32 frame_index
,
2002 core::dimension2d
<u32
> size
, u8 tiles
, video::IVideoDriver
*driver
)
2004 core::dimension2d
<u32
> strip_size
= crack
->getDimension();
2005 core::dimension2d
<u32
> frame_size(strip_size
.Width
, strip_size
.Width
);
2006 core::dimension2d
<u32
> tile_size(size
/ tiles
);
2007 s32 frame_count
= strip_size
.Height
/ strip_size
.Width
;
2008 if (frame_index
>= frame_count
)
2009 frame_index
= frame_count
- 1;
2010 core::rect
<s32
> frame(v2s32(0, frame_index
* frame_size
.Height
), frame_size
);
2011 video::IImage
*result
= nullptr;
2013 // extract crack frame
2014 video::IImage
*crack_tile
= driver
->createImage(video::ECF_A8R8G8B8
, tile_size
);
2017 if (tile_size
== frame_size
) {
2018 crack
->copyTo(crack_tile
, v2s32(0, 0), frame
);
2020 video::IImage
*crack_frame
= driver
->createImage(video::ECF_A8R8G8B8
, frame_size
);
2022 goto exit__has_tile
;
2023 crack
->copyTo(crack_frame
, v2s32(0, 0), frame
);
2024 crack_frame
->copyToScaling(crack_tile
);
2025 crack_frame
->drop();
2031 result
= driver
->createImage(video::ECF_A8R8G8B8
, size
);
2033 goto exit__has_tile
;
2035 for (u8 i
= 0; i
< tiles
; i
++)
2036 for (u8 j
= 0; j
< tiles
; j
++)
2037 crack_tile
->copyTo(result
, v2s32(i
* tile_size
.Width
, j
* tile_size
.Height
));
2044 static void draw_crack(video::IImage
*crack
, video::IImage
*dst
,
2045 bool use_overlay
, s32 frame_count
, s32 progression
,
2046 video::IVideoDriver
*driver
, u8 tiles
)
2048 // Dimension of destination image
2049 core::dimension2d
<u32
> dim_dst
= dst
->getDimension();
2050 // Limit frame_count
2051 if (frame_count
> (s32
) dim_dst
.Height
)
2052 frame_count
= dim_dst
.Height
;
2053 if (frame_count
< 1)
2055 // Dimension of the scaled crack stage,
2056 // which is the same as the dimension of a single destination frame
2057 core::dimension2d
<u32
> frame_size(
2059 dim_dst
.Height
/ frame_count
2061 video::IImage
*crack_scaled
= create_crack_image(crack
, progression
,
2062 frame_size
, tiles
, driver
);
2066 auto blit
= use_overlay
? blit_with_alpha_overlay
: blit_with_alpha
;
2067 for (s32 i
= 0; i
< frame_count
; ++i
) {
2068 v2s32
dst_pos(0, frame_size
.Height
* i
);
2069 blit(crack_scaled
, dst
, v2s32(0,0), dst_pos
, frame_size
);
2072 crack_scaled
->drop();
2075 void brighten(video::IImage
*image
)
2080 core::dimension2d
<u32
> dim
= image
->getDimension();
2082 for (u32 y
=0; y
<dim
.Height
; y
++)
2083 for (u32 x
=0; x
<dim
.Width
; x
++)
2085 video::SColor c
= image
->getPixel(x
,y
);
2086 c
.setRed(0.5 * 255 + 0.5 * (float)c
.getRed());
2087 c
.setGreen(0.5 * 255 + 0.5 * (float)c
.getGreen());
2088 c
.setBlue(0.5 * 255 + 0.5 * (float)c
.getBlue());
2089 image
->setPixel(x
,y
,c
);
2093 u32
parseImageTransform(const std::string
& s
)
2095 int total_transform
= 0;
2097 std::string transform_names
[8];
2098 transform_names
[0] = "i";
2099 transform_names
[1] = "r90";
2100 transform_names
[2] = "r180";
2101 transform_names
[3] = "r270";
2102 transform_names
[4] = "fx";
2103 transform_names
[6] = "fy";
2105 std::size_t pos
= 0;
2106 while(pos
< s
.size())
2109 for (int i
= 0; i
<= 7; ++i
)
2111 const std::string
&name_i
= transform_names
[i
];
2113 if (s
[pos
] == ('0' + i
))
2120 if (!(name_i
.empty()) && lowercase(s
.substr(pos
, name_i
.size())) == name_i
) {
2122 pos
+= name_i
.size();
2129 // Multiply total_transform and transform in the group D4
2132 new_total
= (transform
+ total_transform
) % 4;
2134 new_total
= (transform
- total_transform
+ 8) % 4;
2135 if ((transform
>= 4) ^ (total_transform
>= 4))
2138 total_transform
= new_total
;
2140 return total_transform
;
2143 core::dimension2d
<u32
> imageTransformDimension(u32 transform
, core::dimension2d
<u32
> dim
)
2145 if (transform
% 2 == 0)
2148 return core::dimension2d
<u32
>(dim
.Height
, dim
.Width
);
2151 void imageTransform(u32 transform
, video::IImage
*src
, video::IImage
*dst
)
2153 if (src
== NULL
|| dst
== NULL
)
2156 core::dimension2d
<u32
> dstdim
= dst
->getDimension();
2159 assert(dstdim
== imageTransformDimension(transform
, src
->getDimension()));
2160 assert(transform
<= 7);
2163 Compute the transformation from source coordinates (sx,sy)
2164 to destination coordinates (dx,dy).
2168 if (transform
== 0) // identity
2169 sxn
= 0, syn
= 2; // sx = dx, sy = dy
2170 else if (transform
== 1) // rotate by 90 degrees ccw
2171 sxn
= 3, syn
= 0; // sx = (H-1) - dy, sy = dx
2172 else if (transform
== 2) // rotate by 180 degrees
2173 sxn
= 1, syn
= 3; // sx = (W-1) - dx, sy = (H-1) - dy
2174 else if (transform
== 3) // rotate by 270 degrees ccw
2175 sxn
= 2, syn
= 1; // sx = dy, sy = (W-1) - dx
2176 else if (transform
== 4) // flip x
2177 sxn
= 1, syn
= 2; // sx = (W-1) - dx, sy = dy
2178 else if (transform
== 5) // flip x then rotate by 90 degrees ccw
2179 sxn
= 2, syn
= 0; // sx = dy, sy = dx
2180 else if (transform
== 6) // flip y
2181 sxn
= 0, syn
= 3; // sx = dx, sy = (H-1) - dy
2182 else if (transform
== 7) // flip y then rotate by 90 degrees ccw
2183 sxn
= 3, syn
= 1; // sx = (H-1) - dy, sy = (W-1) - dx
2185 for (u32 dy
=0; dy
<dstdim
.Height
; dy
++)
2186 for (u32 dx
=0; dx
<dstdim
.Width
; dx
++)
2188 u32 entries
[4] = {dx
, dstdim
.Width
-1-dx
, dy
, dstdim
.Height
-1-dy
};
2189 u32 sx
= entries
[sxn
];
2190 u32 sy
= entries
[syn
];
2191 video::SColor c
= src
->getPixel(sx
,sy
);
2192 dst
->setPixel(dx
,dy
,c
);
2196 video::ITexture
* TextureSource::getNormalTexture(const std::string
&name
)
2198 if (isKnownSourceImage("override_normal.png"))
2199 return getTexture("override_normal.png");
2200 std::string fname_base
= name
;
2201 static const char *normal_ext
= "_normal.png";
2202 static const u32 normal_ext_size
= strlen(normal_ext
);
2203 size_t pos
= fname_base
.find('.');
2204 std::string fname_normal
= fname_base
.substr(0, pos
) + normal_ext
;
2205 if (isKnownSourceImage(fname_normal
)) {
2206 // look for image extension and replace it
2208 while ((i
= fname_base
.find('.', i
)) != std::string::npos
) {
2209 fname_base
.replace(i
, 4, normal_ext
);
2210 i
+= normal_ext_size
;
2212 return getTexture(fname_base
);
2217 video::SColor
TextureSource::getTextureAverageColor(const std::string
&name
)
2219 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
2220 video::SColor
c(0, 0, 0, 0);
2221 video::ITexture
*texture
= getTexture(name
);
2222 video::IImage
*image
= driver
->createImage(texture
,
2223 core::position2d
<s32
>(0, 0),
2224 texture
->getOriginalSize());
2229 core::dimension2d
<u32
> dim
= image
->getDimension();
2232 step
= dim
.Width
/ 16;
2233 for (u16 x
= 0; x
< dim
.Width
; x
+= step
) {
2234 for (u16 y
= 0; y
< dim
.Width
; y
+= step
) {
2235 c
= image
->getPixel(x
,y
);
2236 if (c
.getAlpha() > 0) {
2246 c
.setRed(tR
/ total
);
2247 c
.setGreen(tG
/ total
);
2248 c
.setBlue(tB
/ total
);
2255 video::ITexture
*TextureSource::getShaderFlagsTexture(bool normalmap_present
)
2257 std::string tname
= "__shaderFlagsTexture";
2258 tname
+= normalmap_present
? "1" : "0";
2260 if (isKnownSourceImage(tname
)) {
2261 return getTexture(tname
);
2264 video::IVideoDriver
*driver
= RenderingEngine::get_video_driver();
2265 video::IImage
*flags_image
= driver
->createImage(
2266 video::ECF_A8R8G8B8
, core::dimension2d
<u32
>(1, 1));
2267 sanity_check(flags_image
!= NULL
);
2268 video::SColor
c(255, normalmap_present
? 255 : 0, 0, 0);
2269 flags_image
->setPixel(0, 0, c
);
2270 insertSourceImage(tname
, flags_image
);
2271 flags_image
->drop();
2272 return getTexture(tname
);
2276 std::vector
<std::string
> getTextureDirs()
2278 return fs::GetRecursiveDirs(g_settings
->get("texture_path"));