aboutsummaryrefslogtreecommitdiff
path: root/src/util/serialize.cpp
blob: 5b276668d80a63e6b642abdca12eeee65337d483 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "serialize.h"
#include "pointer.h"
#include "porting.h"
#include "util/string.h"
#include "exceptions.h"
#include "irrlichttypes.h"

#include <sstream>
#include <iomanip>
#include <vector>

FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN;

////
//// BufReader
////

bool BufReader::getStringNoEx(std::string *val)
{
	u16 num_chars;
	if (!getU16NoEx(&num_chars))
		return false;

	if (pos + num_chars > size) {
		pos -= sizeof(num_chars);
		return false;
	}

	val->assign((const char *)data + pos, num_chars);
	pos += num_chars;

	return true;
}

bool BufReader::getWideStringNoEx(std::wstring *val)
{
	u16 num_chars;
	if (!getU16NoEx(&num_chars))
		return false;

	if (pos + num_chars * 2 > size) {
		pos -= sizeof(num_chars);
		return false;
	}

	for (size_t i = 0; i != num_chars; i++) {
		val->push_back(readU16(data + pos));
		pos += 2;
	}

	return true;
}

bool BufReader::getLongStringNoEx(std::string *val)
{
	u32 num_chars;
	if (!getU32NoEx(&num_chars))
		return false;

	if (pos + num_chars > size) {
		pos -= sizeof(num_chars);
		return false;
	}

	val->assign((const char *)data + pos, num_chars);
	pos += num_chars;

	return true;
}

bool BufReader::getRawDataNoEx(void *val, size_t len)
{
	if (pos + len > size)
		return false;

	memcpy(val, data + pos, len);
	pos += len;

	return true;
}


////
//// String
////

std::string serializeString(const std::string &plain)
{
	std::string s;
	char buf[2];

	if (plain.size() > STRING_MAX_LEN)
		throw SerializationError("String too long for serializeString");
	s.reserve(2 + plain.size());

	writeU16((u8 *)&buf[0], plain.size());
	s.append(buf, 2);

	s.append(plain);
	return s;
}

std::string deSerializeString(std::istream &is)
{
	std::string s;
	char buf[2];

	is.read(buf, 2);
	if (is.gcount() != 2)
		throw SerializationError("deSerializeString: size not read");

	u16 s_size = readU16((u8 *)buf);
	if (s_size == 0)
		return s;

	s.resize(s_size);
	is.read(&s[0], s_size);
	if (is.gcount() != s_size)
		throw SerializationError("deSerializeString: couldn't read all chars");

	return s;
}

////
//// Wide String
////

std::string serializeWideString(const std::wstring &plain)
{
	std::string s;
	char buf[2];

	if (plain.size() > WIDE_STRING_MAX_LEN)
		throw SerializationError("String too long for serializeWideString");
	s.reserve(2 + 2 * plain.size());

	writeU16((u8 *)buf, plain.size());
	s.append(buf, 2);

	for (wchar_t i : plain) {
		writeU16((u8 *)buf, i);
		s.append(buf, 2);
	}
	return s;
}

std::wstring deSerializeWideString(std::istream &is)
{
	std::wstring s;
	char buf[2];

	is.read(buf, 2);
	if (is.gcount() != 2)
		throw SerializationError("deSerializeWideString: size not read");

	u16 s_size = readU16((u8 *)buf);
	if (s_size == 0)
		return s;

	s.reserve(s_size);
	for (u32 i = 0; i < s_size; i++) {
		is.read(&buf[0], 2);
		if (is.gcount() != 2) {
			throw SerializationError(
				"deSerializeWideString: couldn't read all chars");
		}

		wchar_t c16 = readU16((u8 *)buf);
		s.append(&c16, 1);
	}
	return s;
}

////
//// Long String
////

std::string serializeLongString(const std::string &plain)
{
	std::string s;
	char buf[4];

	if (plain.size() > LONG_STRING_MAX_LEN)
		throw SerializationError("String too long for serializeLongString");
	s.reserve(4 + plain.size());

	writeU32((u8*)&buf[0], plain.size());
	s.append(buf, 4);
	s.append(plain);
	return s;
}

std::string deSerializeLongString(std::istream &is)
{
	std::string s;
	char buf[4];

	is.read(buf, 4);
	if (is.gcount() != 4)
		throw SerializationError("deSerializeLongString: size not read");

	u32 s_size = readU32((u8 *)buf);
	if (s_size == 0)
		return s;

	// We don't really want a remote attacker to force us to allocate 4GB...
	if (s_size > LONG_STRING_MAX_LEN) {
		throw SerializationError("deSerializeLongString: "
			"string too long: " + itos(s_size) + " bytes");
	}

	s.resize(s_size);
	is.read(&s[0], s_size);
	if ((u32)is.gcount() != s_size)
		throw SerializationError("deSerializeLongString: couldn't read all chars");

	return s;
}

////
//// JSON
////

std::string serializeJsonString(const std::string &plain)
{
	std::ostringstream os(std::ios::binary);
	os << "\"";

	for (char c : plain) {
		switch (c) {
			case '"':
				os << "\\\"";
				break;
			case '\\':
				os << "\\\\";
				break;
			case '/':
				os << "\\/";
				break;
			case '\b':
				os << "\\b";
				break;
			case '\f':
				os << "\\f";
				break;
			case '\n':
				os << "\\n";
				break;
			case '\r':
				os << "\\r";
				break;
			case '\t':
				os << "\\t";
				break;
			default: {
				if (c >= 32 && c <= 126) {
					os << c;
				} else {
					u32 cnum = (u8)c;
					os << "\\u" << std::hex << std::setw(4)
						<< std::setfill('0') << cnum;
				}
				break;
			}
		}
	}

	os << "\"";
	return os.str();
}

std::string deSerializeJsonString(std::istream &is)
{
	std::ostringstream os(std::ios::binary);
	char c, c2;

	// Parse initial doublequote
	is >> c;
	if (c != '"')
		throw SerializationError("JSON string must start with doublequote");

	// Parse characters
	for (;;) {
		c = is.get();
		if (is.eof())
			throw SerializationError("JSON string ended prematurely");

		if (c == '"') {
			return os.str();
		}

		if (c == '\\') {
			c2 = is.get();
			if (is.eof())
				throw SerializationError("JSON string ended prematurely");
			switch (c2) {
				case 'b':
					os << '\b';
					break;
				case 'f':
					os << '\f';
					break;
				case 'n':
					os << '\n';
					break;
				case 'r':
					os << '\r';
					break;
				case 't':
					os << '\t';
					break;
				case 'u': {
					int hexnumber;
					char hexdigits[4 + 1];

					is.read(hexdigits, 4);
					if (is.eof())
						throw SerializationError("JSON string ended prematurely");
					hexdigits[4] = 0;

					std::istringstream tmp_is(hexdigits, std::ios::binary);
					tmp_is >> std::hex >> hexnumber;
					os << (char)hexnumber;
					break;
				}
				default:
					os << c2;
					break;
			}
		} else {
			os << c;
		}
	}

	return os.str();
}

std::string serializeJsonStringIfNeeded(const std::string &s)
{
	for (size_t i = 0; i < s.size(); ++i) {
		if (s[i] <= 0x1f || s[i] >= 0x7f || s[i] == ' ' || s[i] == '\"')
			return serializeJsonString(s);
	}
	return s;
}

std::string deSerializeJsonStringIfNeeded(std::istream &is)
{
	std::ostringstream tmp_os;
	bool expect_initial_quote = true;
	bool is_json = false;
	bool was_backslash = false;
	for (;;) {
		char c = is.get();
		if (is.eof())
			break;

		if (expect_initial_quote && c == '"') {
			tmp_os << c;
			is_json = true;
		} else if(is_json) {
			tmp_os << c;
			if (was_backslash)
				was_backslash = false;
			else if (c == '\\')
				was_backslash = true;
			else if (c == '"')
				break; // Found end of string
		} else {
			if (c == ' ') {
				// Found end of word
				is.unget();
				break;
			}

			tmp_os << c;
		}
		expect_initial_quote = false;
	}
	if (is_json) {
		std::istringstream tmp_is(tmp_os.str(), std::ios::binary);
		return deSerializeJsonString(tmp_is);
	}

	return tmp_os.str();
}

////
//// String/Struct conversions
////

bool deSerializeStringToStruct(std::string valstr,
	std::string format, void *out, size_t olen)
{
	size_t len = olen;
	std::vector<std::string *> strs_alloced;
	std::string *str;
	char *f, *snext;
	size_t pos;

	char *s = &valstr[0];
	char *buf = new char[len];
	char *bufpos = buf;

	char *fmtpos, *fmt = &format[0];
	while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
		fmt = nullptr;

		bool is_unsigned = false;
		int width = 0;
		char valtype = *f;

		width = (int)strtol(f + 1, &f, 10);
		if (width && valtype == 's')
			valtype = 'i';

		switch (valtype) {
			case 'u':
				is_unsigned = true;
				/* FALLTHROUGH */
			case 'i':
				if (width == 16) {
					bufpos += PADDING(bufpos, u16);
					if ((bufpos - buf) + sizeof(u16) <= len) {
						if (is_unsigned)
							*(u16 *)bufpos = (u16)strtoul(s, &s, 10);
						else
							*(s16 *)bufpos = (s16)strtol(s, &s, 10);
					}
					bufpos += sizeof(u16);
				} else if (width == 32) {
					bufpos += PADDING(bufpos, u32);
					if ((bufpos - buf) + sizeof(u32) <= len) {
						if (is_unsigned)
							*(u32 *)bufpos = (u32)strtoul(s, &s, 10);
						else
							*(s32 *)bufpos = (s32)strtol(s, &s, 10);
					}
					bufpos += sizeof(u32);
				} else if (width == 64) {
					bufpos += PADDING(bufpos, u64);
					if ((bufpos - buf) + sizeof(u64) <= len) {
						if (is_unsigned)
							*(u64 *)bufpos = (u64)strtoull(s, &s, 10);
						else
							*(s64 *)bufpos = (s64)strtoll(s, &s, 10);
					}
					bufpos += sizeof(u64);
				}
				s = strchr(s, ',');
				break;
			case 'b':
				snext = strchr(s, ',');
				if (snext)
					*snext++ = 0;

				bufpos += PADDING(bufpos, bool);
				if ((bufpos - buf) + sizeof(bool) <= len)
					*(bool *)bufpos = is_yes(std::string(s));
				bufpos += sizeof(bool);

				s = snext;
				break;
			case 'f':
				bufpos += PADDING(bufpos, float);
				if ((bufpos - buf) + sizeof(float) <= len)
					*(float *)bufpos = strtof(s, &s);
				bufpos += sizeof(float);

				s = strchr(s, ',');
				break;
			case 's':
				while (*s == ' ' || *s == '\t')
					s++;
				if (*s++ != '"') //error, expected string
					goto fail;
				snext = s;

				while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
					snext++;
				*snext++ = 0;

				bufpos += PADDING(bufpos, std::string *);

				str = new std::string(s);
				pos = 0;
				while ((pos = str->find("\\\"", pos)) != std::string::npos)
					str->erase(pos, 1);

				if ((bufpos - buf) + sizeof(std::string *) <= len)
					*(std::string **)bufpos = str;
				bufpos += sizeof(std::string *);
				strs_alloced.push_back(str);

				s = *snext ? snext + 1 : nullptr;
				break;
			case 'v':
				while (*s == ' ' || *s == '\t')
					s++;
				if (*s++ != '(') //error, expected vector
					goto fail;

				if (width == 2) {
					bufpos += PADDING(bufpos, v2f);

					if ((bufpos - buf) + sizeof(v2f) <= len) {
					v2f *v = (v2f *)bufpos;
						v->X = strtof(s, &s);
						s++;
						v->Y = strtof(s, &s);
					}

					bufpos += sizeof(v2f);
				} else if (width == 3) {
					bufpos += PADDING(bufpos, v3f);
					if ((bufpos - buf) + sizeof(v3f) <= len) {
						v3f *v = (v3f *)bufpos;
						v->X = strtof(s, &s);
						s++;
						v->Y = strtof(s, &s);
						s++;
						v->Z = strtof(s, &s);
					}

					bufpos += sizeof(v3f);
				}
				s = strchr(s, ',');
				break;
			default: //error, invalid format specifier
				goto fail;
		}

		if (s && *s == ',')
			s++;

		if ((size_t)(bufpos - buf) > len) //error, buffer too small
			goto fail;
	}

	if (f && *f) { //error, mismatched number of fields and values
fail:
		for (size_t i = 0; i != strs_alloced.size(); i++)
			delete strs_alloced[i];
		delete[] buf;
		return false;
	}

	memcpy(out, buf, olen);
	delete[] buf;
	return true;
}

// Casts *buf to a signed or unsigned fixed-width integer of 'w' width
#define SIGN_CAST(w, buf) (is_unsigned ? *((u##w *) buf) : *((s##w *) buf))

bool serializeStructToString(std::string *out,
	std::string format, void *value)
{
	std::ostringstream os;
	std::string str;
	char *f;
	size_t strpos;

	char *bufpos = (char *) value;
	char *fmtpos, *fmt = &format[0];
	while ((f = strtok_r(fmt, ",", &fmtpos))) {
		fmt = nullptr;
		bool is_unsigned = false;
		int width = 0;
		char valtype = *f;

		width = (int)strtol(f + 1, &f, 10);
		if (width && valtype == 's')
			valtype = 'i';

		switch (valtype) {
			case 'u':
				is_unsigned = true;
				/* FALLTHROUGH */
			case 'i':
				if (width == 16) {
					bufpos += PADDING(bufpos, u16);
					os << SIGN_CAST(16, bufpos);
					bufpos += sizeof(u16);
				} else if (width == 32) {
					bufpos += PADDING(bufpos, u32);
					os << SIGN_CAST(32, bufpos);
					bufpos += sizeof(u32);
				} else if (width == 64) {
					bufpos += PADDING(bufpos, u64);
					os << SIGN_CAST(64, bufpos);
					bufpos += sizeof(u64);
				}
				break;
			case 'b':
				bufpos += PADDING(bufpos, bool);
				os << std::boolalpha << *((bool *) bufpos);
				bufpos += sizeof(bool);
				break;
			case 'f':
				bufpos += PADDING(bufpos, float);
				os << *((float *) bufpos);
				bufpos += sizeof(float);
				break;
			case 's':
				bufpos += PADDING(bufpos, std::string *);
				str = **((std::string **) bufpos);

				strpos = 0;
				while ((strpos = str.find('"', strpos)) != std::string::npos) {
					str.insert(strpos, 1, '\\');
					strpos += 2;
				}

				os << str;
				bufpos += sizeof(std::string *);
				break;
			case 'v':
				if (width == 2) {
					bufpos += PADDING(bufpos, v2f);
					v2f *v = (v2f *) bufpos;
					os << '(' << v->X << ", " << v->Y << ')';
					bufpos += sizeof(v2f);
				} else {
					bufpos += PADDING(bufpos, v3f);
					v3f *v = (v3f *) bufpos;
					os << '(' << v->X << ", " << v->Y << ", " << v->Z << ')';
					bufpos += sizeof(v3f);
				}
				break;
			default:
				return false;
		}
		os << ", ";
	}
	*out = os.str();

	// Trim off the trailing comma and space
	if (out->size() >= 2)
		out->resize(out->size() - 2);

	return true;
}

#undef SIGN_CAST

////
//// Other
////

std::string serializeHexString(const std::string &data, bool insert_spaces)
{
	std::string result;
	result.reserve(data.size() * (2 + insert_spaces));

	static const char hex_chars[] = "0123456789abcdef";

	const size_t len = data.size();
	for (size_t i = 0; i != len; i++) {
		u8 byte = data[i];
		result.push_back(hex_chars[(byte >> 4) & 0x0F]);
		result.push_back(hex_chars[(byte >> 0) & 0x0F]);
		if (insert_spaces && i != len - 1)
			result.push_back(' ');
	}

	return result;
}
pan> (need_to_grab) toadd->grab(); m_images[name] = toadd; } video::IImage* get(const std::string &name) { std::map<std::string, video::IImage*>::iterator n; n = m_images.find(name); if (n != m_images.end()) return n->second; return NULL; } // Primarily fetches from cache, secondarily tries to read from filesystem video::IImage *getOrLoad(const std::string &name) { std::map<std::string, video::IImage*>::iterator n; n = m_images.find(name); if (n != m_images.end()){ n->second->grab(); // Grab for caller return n->second; } video::IVideoDriver *driver = RenderingEngine::get_video_driver(); std::string path = getTexturePath(name); if (path.empty()) { infostream<<"SourceImageCache::getOrLoad(): No path found for \"" <<name<<"\""<<std::endl; return NULL; } infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path <<"\""<<std::endl; video::IImage *img = driver->createImageFromFile(path.c_str()); if (img){ m_images[name] = img; img->grab(); // Grab for caller } return img; } private: std::map<std::string, video::IImage*> m_images; }; /* TextureSource */ class TextureSource : public IWritableTextureSource { public: TextureSource(); virtual ~TextureSource(); /* Example case: Now, assume a texture with the id 1 exists, and has the name "stone.png^mineral1". Then a random thread calls getTextureId for a texture called "stone.png^mineral1^crack0". ...Now, WTF should happen? Well: - getTextureId strips off stuff recursively from the end until the remaining part is found, or nothing is left when something is stripped out But it is slow to search for textures by names and modify them like that? - ContentFeatures is made to contain ids for the basic plain textures - Crack textures can be slow by themselves, but the framework must be fast. Example case #2: - Assume a texture with the id 1 exists, and has the name "stone.png^mineral_coal.png". - Now getNodeTile() stumbles upon a node which uses texture id 1, and determines that MATERIAL_FLAG_CRACK must be applied to the tile - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and has received the current crack level 0 from the client. It finds out the name of the texture with getTextureName(1), appends "^crack0" to it and gets a new texture id with getTextureId("stone.png^mineral_coal.png^crack0"). */ /* Gets a texture id from cache or - if main thread, generates the texture, adds to cache and returns id. - if other thread, adds to request queue and waits for main thread. The id 0 points to a NULL texture. It is returned in case of error. */ u32 getTextureId(const std::string &name); // Finds out the name of a cached texture. std::string getTextureName(u32 id); /* If texture specified by the name pointed by the id doesn't exist, create it, then return the cached texture. Can be called from any thread. If called from some other thread and not found in cache, the call is queued to the main thread for processing. */ video::ITexture* getTexture(u32 id); video::ITexture* getTexture(const std::string &name, u32 *id = NULL); /* Get a texture specifically intended for mesh application, i.e. not HUD, compositing, or other 2D use. This texture may be a different size and may have had additional filters applied. */ video::ITexture* getTextureForMesh(const std::string &name, u32 *id); virtual Palette* getPalette(const std::string &name); bool isKnownSourceImage(const std::string &name) { bool is_known = false; bool cache_found = m_source_image_existence.get(name, &is_known); if (cache_found) return is_known; // Not found in cache; find out if a local file exists is_known = (!getTexturePath(name).empty()); m_source_image_existence.set(name, is_known); return is_known; } // Processes queued texture requests from other threads. // Shall be called from the main thread. void processQueue(); // Insert an image into the cache without touching the filesystem. // Shall be called from the main thread. void insertSourceImage(const std::string &name, video::IImage *img); // Rebuild images and textures from the current set of source images // Shall be called from the main thread. void rebuildImagesAndTextures(); // Render a mesh to a texture. // Returns NULL if render-to-texture failed. // Shall be called from the main thread. video::ITexture* generateTextureFromMesh( const TextureFromMeshParams &params); video::ITexture* getNormalTexture(const std::string &name); video::SColor getTextureAverageColor(const std::string &name); video::ITexture *getShaderFlagsTexture(bool normamap_present); private: // The id of the thread that is allowed to use irrlicht directly std::thread::id m_main_thread; // Cache of source images // This should be only accessed from the main thread SourceImageCache m_sourcecache; // Generate a texture u32 generateTexture(const std::string &name); // Generate image based on a string like "stone.png" or "[crack:1:0". // if baseimg is NULL, it is created. Otherwise stuff is made on it. bool generateImagePart(std::string part_of_name, video::IImage *& baseimg); /*! Generates an image from a full string like * "stone.png^mineral_coal.png^[crack:1:0". * Shall be called from the main thread. * The returned Image should be dropped. */ video::IImage* generateImage(const std::string &name); // Thread-safe cache of what source images are known (true = known) MutexedMap<std::string, bool> m_source_image_existence; // A texture id is index in this array. // The first position contains a NULL texture. std::vector<TextureInfo> m_textureinfo_cache; // Maps a texture name to an index in the former. std::map<std::string, u32> m_name_to_id; // The two former containers are behind this mutex std::mutex m_textureinfo_cache_mutex; // Queued texture fetches (to be processed by the main thread) RequestQueue<std::string, u32, u8, u8> m_get_texture_queue; // Textures that have been overwritten with other ones // but can't be deleted because the ITexture* might still be used std::vector<video::ITexture*> m_texture_trash; // Maps image file names to loaded palettes. std::unordered_map<std::string, Palette> m_palettes; // Cached settings needed for making textures from meshes bool m_setting_trilinear_filter; bool m_setting_bilinear_filter; bool m_setting_anisotropic_filter; }; IWritableTextureSource *createTextureSource() { return new TextureSource(); } TextureSource::TextureSource() { m_main_thread = std::this_thread::get_id(); // Add a NULL TextureInfo as the first index, named "" m_textureinfo_cache.emplace_back(""); m_name_to_id[""] = 0; // Cache some settings // Note: Since this is only done once, the game must be restarted // for these settings to take effect m_setting_trilinear_filter = g_settings->getBool("trilinear_filter"); m_setting_bilinear_filter = g_settings->getBool("bilinear_filter"); m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter"); } TextureSource::~TextureSource() { video::IVideoDriver *driver = RenderingEngine::get_video_driver(); unsigned int textures_before = driver->getTextureCount(); for (const auto &iter : m_textureinfo_cache) { //cleanup texture if (iter.texture) driver->removeTexture(iter.texture); } m_textureinfo_cache.clear(); for (auto t : m_texture_trash) { //cleanup trashed texture driver->removeTexture(t); } infostream << "~TextureSource() "<< textures_before << "/" << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) { //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl; { /* See if texture already exists */ MutexAutoLock lock(m_textureinfo_cache_mutex); std::map<std::string, u32>::iterator n; n = m_name_to_id.find(name); if (n != m_name_to_id.end()) { return n->second; } } /* Get texture */ if (std::this_thread::get_id() == m_main_thread) { return generateTexture(name); } infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl; // We're gonna ask the result to be put into here static ResultQueue<std::string, u32, u8, u8> result_queue; // Throw a request in m_get_texture_queue.add(name, 0, 0, &result_queue); try { while(true) { // Wait result for a second GetResult<std::string, u32, u8, u8> result = result_queue.pop_front(1000); if (result.key == name) { return result.item; } } } catch(ItemNotFoundException &e) { errorstream << "Waiting for texture " << name << " timed out." << std::endl; return 0; } infostream << "getTextureId(): Failed" << std::endl; return 0; } // Draw an image on top of an another one, using the alpha channel of the // source image static void blit_with_alpha(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size); // Like blit_with_alpha, but only modifies destination pixels that // are fully opaque static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size); // Apply a color to an image. Uses an int (0-255) to calculate the ratio. // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the // color alpha with the destination alpha. // Otherwise, any pixels that are not fully transparent get the color alpha. static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, const video::SColor &color, int ratio, bool keep_alpha); // paint a texture using the given color static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, const video::SColor &color); // Apply a mask to an image static void apply_mask(video::IImage *mask, video::IImage *dst, v2s32 mask_pos, v2s32 dst_pos, v2u32 size); // Draw or overlay a crack static void draw_crack(video::IImage *crack, video::IImage *dst, bool use_overlay, s32 frame_count, s32 progression, video::IVideoDriver *driver); // Brighten image void brighten(video::IImage *image); // Parse a transform name u32 parseImageTransform(const std::string& s); // Apply transform to image dimension core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim); // Apply transform to image data void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); /* This method generates all the textures */ u32 TextureSource::generateTexture(const std::string &name) { //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl; // Empty name means texture 0 if (name.empty()) { infostream<<"generateTexture(): name is empty"<<std::endl; return 0; } { /* See if texture already exists */ MutexAutoLock lock(m_textureinfo_cache_mutex); std::map<std::string, u32>::iterator n; n = m_name_to_id.find(name); if (n != m_name_to_id.end()) { return n->second; } } /* Calling only allowed from main thread */ if (std::this_thread::get_id() != m_main_thread) { errorstream<<"TextureSource::generateTexture() " "called not from main thread"<<std::endl; return 0; } video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); video::IImage *img = generateImage(name); video::ITexture *tex = NULL; if (img != NULL) { #ifdef __ANDROID__ img = Align2Npot2(img, driver); #endif // Create texture from resulting image tex = driver->addTexture(name.c_str(), img); guiScalingCache(io::path(name.c_str()), driver, img); img->drop(); } /* Add texture to caches (add NULL textures too) */ MutexAutoLock lock(m_textureinfo_cache_mutex); u32 id = m_textureinfo_cache.size(); TextureInfo ti(name, tex); m_textureinfo_cache.push_back(ti); m_name_to_id[name] = id; return id; } std::string TextureSource::getTextureName(u32 id) { MutexAutoLock lock(m_textureinfo_cache_mutex); if (id >= m_textureinfo_cache.size()) { errorstream<<"TextureSource::getTextureName(): id="<<id <<" >= m_textureinfo_cache.size()=" <<m_textureinfo_cache.size()<<std::endl; return ""; } return m_textureinfo_cache[id].name; } video::ITexture* TextureSource::getTexture(u32 id) { MutexAutoLock lock(m_textureinfo_cache_mutex); if (id >= m_textureinfo_cache.size()) return NULL; return m_textureinfo_cache[id].texture; } video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) { u32 actual_id = getTextureId(name); if (id){ *id = actual_id; } return getTexture(actual_id); } video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) { return getTexture(name + "^[applyfiltersformesh", id); } Palette* TextureSource::getPalette(const std::string &name) { // Only the main thread may load images sanity_check(std::this_thread::get_id() == m_main_thread); if (name.empty()) return NULL; auto it = m_palettes.find(name); if (it == m_palettes.end()) { // Create palette video::IImage *img = generateImage(name); if (!img) { warningstream << "TextureSource::getPalette(): palette \"" << name << "\" could not be loaded." << std::endl; return NULL; } Palette new_palette; u32 w = img->getDimension().Width; u32 h = img->getDimension().Height; // Real area of the image u32 area = h * w; if (area == 0) return NULL; if (area > 256) { warningstream << "TextureSource::getPalette(): the specified" << " palette image \"" << name << "\" is larger than 256" << " pixels, using the first 256." << std::endl; area = 256; } else if (256 % area != 0) warningstream << "TextureSource::getPalette(): the " << "specified palette image \"" << name << "\" does not " << "contain power of two pixels." << std::endl; // We stretch the palette so it will fit 256 values // This many param2 values will have the same color u32 step = 256 / area; // For each pixel in the image for (u32 i = 0; i < area; i++) { video::SColor c = img->getPixel(i % w, i / w); // Fill in palette with 'step' colors for (u32 j = 0; j < step; j++) new_palette.push_back(c); } img->drop(); // Fill in remaining elements while (new_palette.size() < 256) new_palette.emplace_back(0xFFFFFFFF); m_palettes[name] = new_palette; it = m_palettes.find(name); } if (it != m_palettes.end()) return &((*it).second); return NULL; } void TextureSource::processQueue() { /* Fetch textures */ //NOTE this is only thread safe for ONE consumer thread! if (!m_get_texture_queue.empty()) { GetRequest<std::string, u32, u8, u8> request = m_get_texture_queue.pop(); /*infostream<<"TextureSource::processQueue(): " <<"got texture request with " <<"name=\""<<request.key<<"\"" <<std::endl;*/ m_get_texture_queue.pushResult(request, generateTexture(request.key)); } } void TextureSource::insertSourceImage(const std::string &name, video::IImage *img) { //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl; sanity_check(std::this_thread::get_id() == m_main_thread); m_sourcecache.insert(name, img, true); m_source_image_existence.set(name, true); } void TextureSource::rebuildImagesAndTextures() { MutexAutoLock lock(m_textureinfo_cache_mutex); video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); // Recreate textures for (TextureInfo &ti : m_textureinfo_cache) { video::IImage *img = generateImage(ti.name); #ifdef __ANDROID__ img = Align2Npot2(img, driver); #endif // Create texture from resulting image video::ITexture *t = NULL; if (img) { t = driver->addTexture(ti.name.c_str(), img); guiScalingCache(io::path(ti.name.c_str()), driver, img); img->drop(); } video::ITexture *t_old = ti.texture; // Replace texture ti.texture = t; if (t_old) m_texture_trash.push_back(t_old); } } video::ITexture* TextureSource::generateTextureFromMesh( const TextureFromMeshParams &params) { video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); #ifdef __ANDROID__ const GLubyte* renderstr = glGetString(GL_RENDERER); std::string renderer((char*) renderstr); // use no render to texture hack if ( (renderer.find("Adreno") != std::string::npos) || (renderer.find("Mali") != std::string::npos) || (renderer.find("Immersion") != std::string::npos) || (renderer.find("Tegra") != std::string::npos) || g_settings->getBool("inventory_image_hack") ) { // Get a scene manager scene::ISceneManager *smgr_main = m_device->getSceneManager(); sanity_check(smgr_main); scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); sanity_check(smgr); const float scaling = 0.2; scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true); meshnode->setMaterialFlag(video::EMF_LIGHTING, true); meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, params.camera_position, params.camera_lookat); // second parameter of setProjectionMatrix (isOrthogonal) is ignored camera->setProjectionMatrix(params.camera_projection_matrix, false); smgr->setAmbientLight(params.ambient_light); smgr->addLightSceneNode(0, params.light_position, params.light_color, params.light_radius*scaling); core::dimension2d<u32> screen = driver->getScreenSize(); // Render scene driver->beginScene(true, true, video::SColor(0,0,0,0)); driver->clearZBuffer(); smgr->drawAll(); core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling); irr::video::IImage* rawImage = driver->createImage(irr::video::ECF_A8R8G8B8, partsize); u8* pixels = static_cast<u8*>(rawImage->lock()); if (!pixels) { rawImage->drop(); return NULL; } core::rect<s32> source( screen.Width /2 - (screen.Width * (scaling / 2)), screen.Height/2 - (screen.Height * (scaling / 2)), screen.Width /2 + (screen.Width * (scaling / 2)), screen.Height/2 + (screen.Height * (scaling / 2)) ); glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y, partsize.Width, partsize.Height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); driver->endScene(); // Drop scene manager smgr->drop(); unsigned int pixelcount = partsize.Width*partsize.Height; u8* runptr = pixels; for (unsigned int i=0; i < pixelcount; i++) { u8 B = *runptr; u8 G = *(runptr+1); u8 R = *(runptr+2); u8 A = *(runptr+3); //BGRA -> RGBA *runptr = R; runptr ++; *runptr = G; runptr ++; *runptr = B; runptr ++; *runptr = A; runptr ++; } video::IImage* inventory_image = driver->createImage(irr::video::ECF_A8R8G8B8, params.dim); rawImage->copyToScaling(inventory_image); rawImage->drop(); guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image); video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); inventory_image->drop(); if (rtt == NULL) { errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl; return NULL; } driver->makeColorKeyTexture(rtt, v2s32(0,0)); if (params.delete_texture_on_shutdown) m_texture_trash.push_back(rtt); return rtt; } #endif if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) { static bool warned = false; if (!warned) { errorstream<<"TextureSource::generateTextureFromMesh(): " <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl; warned = true; } return NULL; } // Create render target texture video::ITexture *rtt = driver->addRenderTargetTexture( params.dim, params.rtt_texture_name.c_str(), video::ECF_A8R8G8B8); if (rtt == NULL) { errorstream<<"TextureSource::generateTextureFromMesh(): " <<"addRenderTargetTexture returned NULL."<<std::endl; return NULL; } // Set render target if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) { driver->removeTexture(rtt); errorstream<<"TextureSource::generateTextureFromMesh(): " <<"failed to set render target"<<std::endl; return NULL; } // Get a scene manager scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager(); assert(smgr_main); scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); assert(smgr); scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true); meshnode->setMaterialFlag(video::EMF_LIGHTING, true); meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, params.camera_position, params.camera_lookat); // second parameter of setProjectionMatrix (isOrthogonal) is ignored camera->setProjectionMatrix(params.camera_projection_matrix, false); smgr->setAmbientLight(params.ambient_light); smgr->addLightSceneNode(0, params.light_position, params.light_color, params.light_radius); // Render scene driver->beginScene(true, true, video::SColor(0,0,0,0)); smgr->drawAll(); driver->endScene(); // Drop scene manager smgr->drop(); // Unset render target driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0)); if (params.delete_texture_on_shutdown) m_texture_trash.push_back(rtt); return rtt; } video::IImage* TextureSource::generateImage(const std::string &name) { // Get the base image const char separator = '^'; const char escape = '\\'; const char paren_open = '('; const char paren_close = ')'; // Find last separator in the name s32 last_separator_pos = -1; u8 paren_bal = 0; for (s32 i = name.size() - 1; i >= 0; i--) { if (i > 0 && name[i-1] == escape) continue; switch (name[i]) { case separator: if (paren_bal == 0) { last_separator_pos = i; i = -1; // break out of loop } break; case paren_open: if (paren_bal == 0) { errorstream << "generateImage(): unbalanced parentheses" << "(extranous '(') while generating texture \"" << name << "\"" << std::endl; return NULL; } paren_bal--; break; case paren_close: paren_bal++; break; default: break; } } if (paren_bal > 0) { errorstream << "generateImage(): unbalanced parentheses" << "(missing matching '(') while generating texture \"" << name << "\"" << std::endl; return NULL; } video::IImage *baseimg = NULL; /* If separator was found, make the base image using a recursive call. */ if (last_separator_pos != -1) { baseimg = generateImage(name.substr(0, last_separator_pos)); } /* Parse out the last part of the name of the image and act according to it */ std::string last_part_of_name = name.substr(last_separator_pos + 1); /* If this name is enclosed in parentheses, generate it and blit it onto the base image */ if (last_part_of_name[0] == paren_open && last_part_of_name[last_part_of_name.size() - 1] == paren_close) { std::string name2 = last_part_of_name.substr(1, last_part_of_name.size() - 2); video::IImage *tmp = generateImage(name2); if (!tmp) { errorstream << "generateImage(): " "Failed to generate \"" << name2 << "\"" << std::endl; return NULL; } core::dimension2d<u32> dim = tmp->getDimension(); if (baseimg) { blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); tmp->drop(); } else { baseimg = tmp; } } else if (!generateImagePart(last_part_of_name, baseimg)) { // Generate image according to part of name errorstream << "generateImage(): " "Failed to generate \"" << last_part_of_name << "\"" << std::endl; } // If no resulting image, print a warning if (baseimg == NULL) { errorstream << "generateImage(): baseimg is NULL (attempted to" " create texture \"" << name << "\")" << std::endl; } return baseimg; } #ifdef __ANDROID__ #include <GLES/gl.h> /** * Check and align image to npot2 if required by hardware * @param image image to check for npot2 alignment * @param driver driver to use for image operations * @return image or copy of image aligned to npot2 */ inline u16 get_GL_major_version() { const GLubyte *gl_version = glGetString(GL_VERSION); return (u16) (gl_version[0] - '0'); } video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver) { if (image == NULL) { return image; } core::dimension2d<u32> dim = image->getDimension(); std::string extensions = (char*) glGetString(GL_EXTENSIONS); // Only GLES2 is trusted to correctly report npot support if (get_GL_major_version() > 1 && extensions.find("GL_OES_texture_npot") != std::string::npos) { return image; } unsigned int height = npot2(dim.Height); unsigned int width = npot2(dim.Width); if ((dim.Height == height) && (dim.Width == width)) { return image; } if (dim.Height > height) { height *= 2; } if (dim.Width > width) { width *= 2; } video::IImage *targetimage = driver->createImage(video::ECF_A8R8G8B8, core::dimension2d<u32>(width, height)); if (targetimage != NULL) { image->copyToScaling(targetimage); } image->drop(); return targetimage; } #endif static std::string unescape_string(const std::string &str, const char esc = '\\') { std::string out; size_t pos = 0, cpos; out.reserve(str.size()); while (1) { cpos = str.find_first_of(esc, pos); if (cpos == std::string::npos) { out += str.substr(pos); break; } out += str.substr(pos, cpos - pos) + str[cpos + 1]; pos = cpos + 2; } return out; } bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { const char escape = '\\'; // same as in generateImage() video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); // Stuff starting with [ are special commands if (part_of_name.empty() || part_of_name[0] != '[') { video::IImage *image = m_sourcecache.getOrLoad(part_of_name); #ifdef __ANDROID__ image = Align2Npot2(image, driver); #endif if (image == NULL) { if (!part_of_name.empty()) { // Do not create normalmap dummies if (part_of_name.find("_normal.png") != std::string::npos) { warningstream << "generateImage(): Could not load normal map \"" << part_of_name << "\"" << std::endl; return true; } errorstream << "generateImage(): Could not load image \"" << part_of_name << "\" while building texture; " "Creating a dummy image" << std::endl; } // Just create a dummy image //core::dimension2d<u32> dim(2,2); core::dimension2d<u32> dim(1,1); image = driver->createImage(video::ECF_A8R8G8B8, dim); sanity_check(image != NULL); /*image->setPixel(0,0, video::SColor(255,255,0,0)); image->setPixel(1,0, video::SColor(255,0,255,0)); image->setPixel(0,1, video::SColor(255,0,0,255)); image->setPixel(1,1, video::SColor(255,255,0,255));*/ image->setPixel(0,0, video::SColor(255,myrand()%256, myrand()%256,myrand()%256)); /*image->setPixel(1,0, video::SColor(255,myrand()%256, myrand()%256,myrand()%256)); image->setPixel(0,1, video::SColor(255,myrand()%256, myrand()%256,myrand()%256)); image->setPixel(1,1, video::SColor(255,myrand()%256, myrand()%256,myrand()%256));*/ } // If base image is NULL, load as base. if (baseimg == NULL) { //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl; /* Copy it this way to get an alpha channel. Otherwise images with alpha cannot be blitted on images that don't have alpha in the original file. */ core::dimension2d<u32> dim = image->getDimension(); baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); image->copyTo(baseimg); } // Else blit on base. else { //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl; // Size of the copied area core::dimension2d<u32> dim = image->getDimension(); //core::dimension2d<u32> dim(16,16); // Position to copy the blitted to in the base image core::position2d<s32> pos_to(0,0); // Position to copy the blitted from in the blitted image core::position2d<s32> pos_from(0,0); // Blit /*image->copyToWithAlpha(baseimg, pos_to, core::rect<s32>(pos_from, dim), video::SColor(255,255,255,255), NULL);*/ core::dimension2d<u32> dim_dst = baseimg->getDimension(); if (dim == dim_dst) { blit_with_alpha(image, baseimg, pos_from, pos_to, dim); } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) { // Upscale overlying image video::IImage *scaled_image = RenderingEngine::get_video_driver()-> createImage(video::ECF_A8R8G8B8, dim_dst); image->copyToScaling(scaled_image); blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst); scaled_image->drop(); } else { // Upscale base image video::IImage *scaled_base = RenderingEngine::get_video_driver()-> createImage(video::ECF_A8R8G8B8, dim); baseimg->copyToScaling(scaled_base); baseimg->drop(); baseimg = scaled_base; blit_with_alpha(image, baseimg, pos_from, pos_to, dim); } } //cleanup image->drop(); } else { // A special texture modification /*infostream<<"generateImage(): generating special " <<"modification \""<<part_of_name<<"\"" <<std::endl;*/ /* [crack:N:P [cracko:N:P Adds a cracking texture N = animation frame count, P = crack progression */ if (str_starts_with(part_of_name, "[crack")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } // Crack image number and overlay option bool use_overlay = (part_of_name[6] == 'o'); Strfnd sf(part_of_name); sf.next(":"); s32 frame_count = stoi(sf.next(":")); s32 progression = stoi(sf.next(":")); if (progression >= 0) { /* Load crack image. It is an image with a number of cracking stages horizontally tiled. */ video::IImage *img_crack = m_sourcecache.getOrLoad( "crack_anylength.png"); if (img_crack) { draw_crack(img_crack, baseimg, use_overlay, frame_count, progression, driver); img_crack->drop(); } } } /* [combine:WxH:X,Y=filename:X,Y=filename2 Creates a bigger texture from any amount of smaller ones */ else if (str_starts_with(part_of_name, "[combine")) { Strfnd sf(part_of_name); sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); core::dimension2d<u32> dim(w0,h0); if (baseimg == NULL) { baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); baseimg->fill(video::SColor(0,0,0,0)); } while (!sf.at_end()) { u32 x = stoi(sf.next(",")); u32 y = stoi(sf.next("=")); std::string filename = unescape_string(sf.next_esc(":", escape), escape); infostream<<"Adding \""<<filename <<"\" to combined ("<<x<<","<<y<<")" <<std::endl; video::IImage *img = generateImage(filename); if (img) { core::dimension2d<u32> dim = img->getDimension(); infostream<<"Size "<<dim.Width <<"x"<<dim.Height<<std::endl; core::position2d<s32> pos_base(x, y); video::IImage *img2 = driver->createImage(video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); /*img2->copyToWithAlpha(baseimg, pos_base, core::rect<s32>(v2s32(0,0), dim), video::SColor(255,255,255,255), NULL);*/ blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); img2->drop(); } else { errorstream << "generateImagePart(): Failed to load image \"" << filename << "\" for [combine" << std::endl; } } } /* [brighten */ else if (str_starts_with(part_of_name, "[brighten")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg==NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } brighten(baseimg); } /* [noalpha Make image completely opaque. Used for the leaves texture when in old leaves mode, so that the transparent parts don't look completely black when simple alpha channel is used for rendering. */ else if (str_starts_with(part_of_name, "[noalpha")) { if (baseimg == NULL){ errorstream<<"generateImagePart(): baseimg==NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } core::dimension2d<u32> dim = baseimg->getDimension(); // Set alpha to full for (u32 y=0; y<dim.Height; y++) for (u32 x=0; x<dim.Width; x++) { video::SColor c = baseimg->getPixel(x,y); c.setAlpha(255); baseimg->setPixel(x,y,c); } } /* [makealpha:R,G,B Convert one color to transparent. */ else if (str_starts_with(part_of_name, "[makealpha:")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } Strfnd sf(part_of_name.substr(11)); u32 r1 = stoi(sf.next(",")); u32 g1 = stoi(sf.next(",")); u32 b1 = stoi(sf.next("")); core::dimension2d<u32> dim = baseimg->getDimension(); /*video::IImage *oldbaseimg = baseimg; baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); oldbaseimg->copyTo(baseimg); oldbaseimg->drop();*/ // Set alpha to full for (u32 y=0; y<dim.Height; y++) for (u32 x=0; x<dim.Width; x++) { video::SColor c = baseimg->getPixel(x,y); u32 r = c.getRed(); u32 g = c.getGreen(); u32 b = c.getBlue(); if (!(r == r1 && g == g1 && b == b1)) continue; c.setAlpha(0); baseimg->setPixel(x,y,c); } } /* [transformN Rotates and/or flips the image. N can be a number (between 0 and 7) or a transform name. Rotations are counter-clockwise. 0 I identity 1 R90 rotate by 90 degrees 2 R180 rotate by 180 degrees 3 R270 rotate by 270 degrees 4 FX flip X 5 FXR90 flip X then rotate by 90 degrees 6 FY flip Y 7 FYR90 flip Y then rotate by 90 degrees Note: Transform names can be concatenated to produce their product (applies the first then the second). The resulting transform will be equivalent to one of the eight existing ones, though (see: dihedral group). */ else if (str_starts_with(part_of_name, "[transform")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } u32 transform = parseImageTransform(part_of_name.substr(10)); core::dimension2d<u32> dim = imageTransformDimension( transform, baseimg->getDimension()); video::IImage *image = driver->createImage( baseimg->getColorFormat(), dim); sanity_check(image != NULL); imageTransform(transform, baseimg, image); baseimg->drop(); baseimg = image; } /* [inventorycube{topimage{leftimage{rightimage In every subimage, replace ^ with &. Create an "inventory cube". NOTE: This should be used only on its own. Example (a grass block (not actually used in game): "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png" */ else if (str_starts_with(part_of_name, "[inventorycube")) { if (baseimg != NULL){ errorstream<<"generateImagePart(): baseimg != NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } str_replace(part_of_name, '&', '^'); Strfnd sf(part_of_name); sf.next("{"); std::string imagename_top = sf.next("{"); std::string imagename_left = sf.next("{"); std::string imagename_right = sf.next("{"); // Generate images for the faces of the cube video::IImage *img_top = generateImage(imagename_top); video::IImage *img_left = generateImage(imagename_left); video::IImage *img_right = generateImage(imagename_right); if (img_top == NULL || img_left == NULL || img_right == NULL) { errorstream << "generateImagePart(): Failed to create textures" << " for inventorycube \"" << part_of_name << "\"" << std::endl; baseimg = generateImage(imagename_top); return true; } #ifdef __ANDROID__ assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height)); assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width)); assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height)); assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width)); assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height)); assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width)); #endif // Create textures from images video::ITexture *texture_top = driver->addTexture( (imagename_top + "__temp__").c_str(), img_top); video::ITexture *texture_left = driver->addTexture( (imagename_left + "__temp__").c_str(), img_left); video::ITexture *texture_right = driver->addTexture( (imagename_right + "__temp__").c_str(), img_right); FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), ""); // Drop images img_top->drop(); img_left->drop(); img_right->drop(); /* Draw a cube mesh into a render target texture */ scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1)); setMeshColor(cube, video::SColor(255, 255, 255, 255)); cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top); cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top); cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right); cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right); cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left); cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left); TextureFromMeshParams params; params.mesh = cube; params.dim.set(64, 64); params.rtt_texture_name = part_of_name + "_RTT"; // We will delete the rtt texture ourselves params.delete_texture_on_shutdown = false; params.camera_position.set(0, 1.0, -1.5); params.camera_position.rotateXZBy(45); params.camera_lookat.set(0, 0, 0); // Set orthogonal projection params.camera_projection_matrix.buildProjectionMatrixOrthoLH( 1.65, 1.65, 0, 100); params.ambient_light.set(1.0, 0.2, 0.2, 0.2); params.light_position.set(10, 100, -50); params.light_color.set(1.0, 0.5, 0.5, 0.5); params.light_radius = 1000; video::ITexture *rtt = generateTextureFromMesh(params); // Drop mesh cube->drop(); // Free textures driver->removeTexture(texture_top); driver->removeTexture(texture_left); driver->removeTexture(texture_right); if (rtt == NULL) { baseimg = generateImage(imagename_top); return true; } // Create image of render target video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim); FATAL_ERROR_IF(!image, "Could not create image of render target"); // Cleanup texture driver->removeTexture(rtt); baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim); if (image) { image->copyTo(baseimg); image->drop(); } } /* [lowpart:percent:filename Adds the lower part of a texture */ else if (str_starts_with(part_of_name, "[lowpart:")) { Strfnd sf(part_of_name); sf.next(":"); u32 percent = stoi(sf.next(":")); std::string filename = unescape_string(sf.next_esc(":", escape), escape); if (baseimg == NULL) baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16)); video::IImage *img = generateImage(filename); if (img) { core::dimension2d<u32> dim = img->getDimension(); core::position2d<s32> pos_base(0, 0); video::IImage *img2 = driver->createImage(video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); core::position2d<s32> clippos(0, 0); clippos.Y = dim.Height * (100-percent) / 100; core::dimension2d<u32> clipdim = dim; clipdim.Height = clipdim.Height * percent / 100 + 1; core::rect<s32> cliprect(clippos, clipdim); img2->copyToWithAlpha(baseimg, pos_base, core::rect<s32>(v2s32(0,0), dim), video::SColor(255,255,255,255), &cliprect); img2->drop(); } } /* [verticalframe:N:I Crops a frame of a vertical animation. N = frame count, I = frame index */ else if (str_starts_with(part_of_name, "[verticalframe:")) { Strfnd sf(part_of_name); sf.next(":"); u32 frame_count = stoi(sf.next(":")); u32 frame_index = stoi(sf.next(":")); if (baseimg == NULL){ errorstream<<"generateImagePart(): baseimg != NULL " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } v2u32 frame_size = baseimg->getDimension(); frame_size.Y /= frame_count; video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, frame_size); if (!img){ errorstream<<"generateImagePart(): Could not create image " <<"for part_of_name=\""<<part_of_name <<"\", cancelling."<<std::endl; return false; } // Fill target image with transparency img->fill(video::SColor(0,0,0,0)); core::dimension2d<u32> dim = frame_size; core::position2d<s32> pos_dst(0, 0); core::position2d<s32> pos_src(0, frame_index * frame_size.Y); baseimg->copyToWithAlpha(img, pos_dst, core::rect<s32>(pos_src, dim), video::SColor(255,255,255,255), NULL); // Replace baseimg baseimg->drop(); baseimg = img; } /* [mask:filename Applies a mask to an image */ else if (str_starts_with(part_of_name, "[mask:")) { if (baseimg == NULL) { errorstream << "generateImage(): baseimg == NULL " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } Strfnd sf(part_of_name); sf.next(":"); std::string filename = unescape_string(sf.next_esc(":", escape), escape); video::IImage *img = generateImage(filename); if (img) { apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0), img->getDimension()); img->drop(); } else { errorstream << "generateImage(): Failed to load \"" << filename << "\"."; } } /* [multiply:color multiplys a given color to any pixel of an image color = color as ColorString */ else if (str_starts_with(part_of_name, "[multiply:")) { Strfnd sf(part_of_name); sf.next(":"); std::string color_str = sf.next(":"); if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg != NULL " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } video::SColor color; if (!parseColorString(color_str, color, false)) return false; apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color); } /* [colorize:color Overlays image with given color color = color as ColorString */ else if (str_starts_with(part_of_name, "[colorize:")) { Strfnd sf(part_of_name); sf.next(":"); std::string color_str = sf.next(":"); std::string ratio_str = sf.next(":"); if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg != NULL " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } video::SColor color; int ratio = -1; bool keep_alpha = false; if (!parseColorString(color_str, color, false)) return false; if (is_number(ratio_str)) ratio = mystoi(ratio_str, 0, 255); else if (ratio_str == "alpha") keep_alpha = true; apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha); } /* [applyfiltersformesh Internal modifier */ else if (str_starts_with(part_of_name, "[applyfiltersformesh")) { // Apply the "clean transparent" filter, if configured. if (g_settings->getBool("texture_clean_transparent")) imageCleanTransparent(baseimg, 127); /* Upscale textures to user's requested minimum size. This is a trick to make * filters look as good on low-res textures as on high-res ones, by making * low-res textures BECOME high-res ones. This is helpful for worlds that * mix high- and low-res textures, or for mods with least-common-denominator * textures that don't have the resources to offer high-res alternatives. */ s32 scaleto = g_settings->getS32("texture_min_size"); if (scaleto > 1) { const core::dimension2d<u32> dim = baseimg->getDimension(); /* Calculate scaling needed to make the shortest texture dimension * equal to the target minimum. If e.g. this is a vertical frames * animation, the short dimension will be the real size. */ if ((dim.Width == 0) || (dim.Height == 0)) { errorstream << "generateImagePart(): Illegal 0 dimension " << "for part_of_name=\""<< part_of_name << "\", cancelling." << std::endl; return false; } u32 xscale = scaleto / dim.Width; u32 yscale = scaleto / dim.Height; u32 scale = (xscale > yscale) ? xscale : yscale; // Never downscale; only scale up by 2x or more. if (scale > 1) { u32 w = scale * dim.Width; u32 h = scale * dim.Height; const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h); video::IImage *newimg = driver->createImage( baseimg->getColorFormat(), newdim); baseimg->copyToScaling(newimg); baseimg->drop(); baseimg = newimg; } } } /* [resize:WxH Resizes the base image to the given dimensions */ else if (str_starts_with(part_of_name, "[resize")) { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg == NULL " << "for part_of_name=\""<< part_of_name << "\", cancelling." << std::endl; return false; } Strfnd sf(part_of_name); sf.next(":"); u32 width = stoi(sf.next("x")); u32 height = stoi(sf.next("")); core::dimension2d<u32> dim(width, height); video::IImage *image = RenderingEngine::get_video_driver()-> createImage(video::ECF_A8R8G8B8, dim); baseimg->copyToScaling(image); baseimg->drop(); baseimg = image; } /* [opacity:R Makes the base image transparent according to the given ratio. R must be between 0 and 255. 0 means totally transparent. 255 means totally opaque. */ else if (str_starts_with(part_of_name, "[opacity:")) { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg == NULL " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } Strfnd sf(part_of_name); sf.next(":"); u32 ratio = mystoi(sf.next(""), 0, 255); core::dimension2d<u32> dim = baseimg->getDimension(); for (u32 y = 0; y < dim.Height; y++) for (u32 x = 0; x < dim.Width; x++) { video::SColor c = baseimg->getPixel(x, y); c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5)); baseimg->setPixel(x, y, c); } } /* [invert:mode Inverts the given channels of the base image. Mode may contain the characters "r", "g", "b", "a". Only the channels that are mentioned in the mode string will be inverted. */ else if (str_starts_with(part_of_name, "[invert:")) { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg == NULL " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } Strfnd sf(part_of_name); sf.next(":"); std::string mode = sf.next(""); u32 mask = 0; if (mode.find('a') != std::string::npos) mask |= 0xff000000UL; if (mode.find('r') != std::string::npos) mask |= 0x00ff0000UL; if (mode.find('g') != std::string::npos) mask |= 0x0000ff00UL; if (mode.find('b') != std::string::npos) mask |= 0x000000ffUL; core::dimension2d<u32> dim = baseimg->getDimension(); for (u32 y = 0; y < dim.Height; y++) for (u32 x = 0; x < dim.Width; x++) { video::SColor c = baseimg->getPixel(x, y); c.color ^= mask; baseimg->setPixel(x, y, c); } } /* [sheet:WxH:X,Y Retrieves a tile at position X,Y (in tiles) from the base image it assumes to be a tilesheet with dimensions W,H (in tiles). */ else if (part_of_name.substr(0,7) == "[sheet:") { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg != NULL " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } Strfnd sf(part_of_name); sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); u32 x0 = stoi(sf.next(",")); u32 y0 = stoi(sf.next(":")); core::dimension2d<u32> img_dim = baseimg->getDimension(); core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0)); video::IImage *img = driver->createImage( video::ECF_A8R8G8B8, tile_dim); if (!img) { errorstream << "generateImagePart(): Could not create image " << "for part_of_name=\"" << part_of_name << "\", cancelling." << std::endl; return false; } img->fill(video::SColor(0,0,0,0)); v2u32 vdim(tile_dim); core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim); baseimg->copyToWithAlpha(img, v2s32(0), rect, video::SColor(255,255,255,255), NULL); // Replace baseimg baseimg->drop(); baseimg = img; } else { errorstream << "generateImagePart(): Invalid " " modification: \"" << part_of_name << "\"" << std::endl; } } return true; } /* Draw an image on top of an another one, using the alpha channel of the source image This exists because IImage::copyToWithAlpha() doesn't seem to always work. */ static void blit_with_alpha(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size) { for (u32 y0=0; y0<size.Y; y0++) for (u32 x0=0; x0<size.X; x0++) { s32 src_x = src_pos.X + x0; s32 src_y = src_pos.Y + y0; s32 dst_x = dst_pos.X + x0; s32 dst_y = dst_pos.Y + y0; video::SColor src_c = src->getPixel(src_x, src_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); dst->setPixel(dst_x, dst_y, dst_c); } } /* Draw an image on top of an another one, using the alpha channel of the source image; only modify fully opaque pixels in destinaion */ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size) { for (u32 y0=0; y0<size.Y; y0++) for (u32 x0=0; x0<size.X; x0++) { s32 src_x = src_pos.X + x0; s32 src_y = src_pos.Y + y0; s32 dst_x = dst_pos.X + x0; s32 dst_y = dst_pos.Y + y0; video::SColor src_c = src->getPixel(src_x, src_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) { dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); dst->setPixel(dst_x, dst_y, dst_c); } } } // This function has been disabled because it is currently unused. // Feel free to re-enable if you find it handy. #if 0 /* Draw an image on top of an another one, using the specified ratio modify all partially-opaque pixels in the destination. */ static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio) { for (u32 y0 = 0; y0 < size.Y; y0++) for (u32 x0 = 0; x0 < size.X; x0++) { s32 src_x = src_pos.X + x0; s32 src_y = src_pos.Y + y0; s32 dst_x = dst_pos.X + x0; s32 dst_y = dst_pos.Y + y0; video::SColor src_c = src->getPixel(src_x, src_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0) { if (ratio == -1) dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); else dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f); dst->setPixel(dst_x, dst_y, dst_c); } } } #endif /* Apply color to destination */ static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, const video::SColor &color, int ratio, bool keep_alpha) { u32 alpha = color.getAlpha(); video::SColor dst_c; if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha dst_c = color; for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { u32 dst_alpha = dst->getPixel(x, y).getAlpha(); if (dst_alpha > 0) { dst_c.setAlpha(dst_alpha * alpha / 255); dst->setPixel(x, y, dst_c); } } } else { // replace the color including the alpha for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) if (dst->getPixel(x, y).getAlpha() > 0) dst->setPixel(x, y, color); } } else { // interpolate between the color and destination float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f); for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { dst_c = dst->getPixel(x, y); if (dst_c.getAlpha() > 0) { dst_c = color.getInterpolated(dst_c, interp); dst->setPixel(x, y, dst_c); } } } } /* Apply color to destination */ static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, const video::SColor &color) { video::SColor dst_c; for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { dst_c = dst->getPixel(x, y); dst_c.set( dst_c.getAlpha(), (dst_c.getRed() * color.getRed()) / 255, (dst_c.getGreen() * color.getGreen()) / 255, (dst_c.getBlue() * color.getBlue()) / 255 ); dst->setPixel(x, y, dst_c); } } /* Apply mask to destination */ static void apply_mask(video::IImage *mask, video::IImage *dst, v2s32 mask_pos, v2s32 dst_pos, v2u32 size) { for (u32 y0 = 0; y0 < size.Y; y0++) { for (u32 x0 = 0; x0 < size.X; x0++) { s32 mask_x = x0 + mask_pos.X; s32 mask_y = y0 + mask_pos.Y; s32 dst_x = x0 + dst_pos.X; s32 dst_y = y0 + dst_pos.Y; video::SColor mask_c = mask->getPixel(mask_x, mask_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); dst_c.color &= mask_c.color; dst->setPixel(dst_x, dst_y, dst_c); } } } static void draw_crack(video::IImage *crack, video::IImage *dst, bool use_overlay, s32 frame_count, s32 progression, video::IVideoDriver *driver) { // Dimension of destination image core::dimension2d<u32> dim_dst = dst->getDimension(); // Dimension of original image core::dimension2d<u32> dim_crack = crack->getDimension(); // Count of crack stages s32 crack_count = dim_crack.Height / dim_crack.Width; // Limit frame_count if (frame_count > (s32) dim_dst.Height) frame_count = dim_dst.Height; if (frame_count < 1) frame_count = 1; // Limit progression if (progression > crack_count-1) progression = crack_count-1; // Dimension of a single crack stage core::dimension2d<u32> dim_crack_cropped( dim_crack.Width, dim_crack.Width ); // Dimension of the scaled crack stage, // which is the same as the dimension of a single destination frame core::dimension2d<u32> dim_crack_scaled( dim_dst.Width, dim_dst.Height / frame_count ); // Create cropped and scaled crack images video::IImage *crack_cropped = driver->createImage( video::ECF_A8R8G8B8, dim_crack_cropped); video::IImage *crack_scaled = driver->createImage( video::ECF_A8R8G8B8, dim_crack_scaled); if (crack_cropped && crack_scaled) { // Crop crack image v2s32 pos_crack(0, progression*dim_crack.Width); crack->copyTo(crack_cropped, v2s32(0,0), core::rect<s32>(pos_crack, dim_crack_cropped)); // Scale crack image by copying crack_cropped->copyToScaling(crack_scaled); // Copy or overlay crack image onto each frame for (s32 i = 0; i < frame_count; ++i) { v2s32 dst_pos(0, dim_crack_scaled.Height * i); if (use_overlay) { blit_with_alpha_overlay(crack_scaled, dst, v2s32(0,0), dst_pos, dim_crack_scaled); } else { blit_with_alpha(crack_scaled, dst, v2s32(0,0), dst_pos, dim_crack_scaled); } } } if (crack_scaled) crack_scaled->drop(); if (crack_cropped) crack_cropped->drop(); } void brighten(video::IImage *image) { if (image == NULL) return; core::dimension2d<u32> dim = image->getDimension(); for (u32 y=0; y<dim.Height; y++) for (u32 x=0; x<dim.Width; x++) { video::SColor c = image->getPixel(x,y); c.setRed(0.5 * 255 + 0.5 * (float)c.getRed()); c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen()); c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue()); image->setPixel(x,y,c); } } u32 parseImageTransform(const std::string& s) { int total_transform = 0; std::string transform_names[8]; transform_names[0] = "i"; transform_names[1] = "r90"; transform_names[2] = "r180"; transform_names[3] = "r270"; transform_names[4] = "fx"; transform_names[6] = "fy"; std::size_t pos = 0; while(pos < s.size()) { int transform = -1; for (int i = 0; i <= 7; ++i) { const std::string &name_i = transform_names[i]; if (s[pos] == ('0' + i)) { transform = i; pos++; break; } if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) { transform = i; pos += name_i.size(); break; } } if (transform < 0) break; // Multiply total_transform and transform in the group D4 int new_total = 0; if (transform < 4) new_total = (transform + total_transform) % 4; else new_total = (transform - total_transform + 8) % 4; if ((transform >= 4) ^ (total_transform >= 4)) new_total += 4; total_transform = new_total; } return total_transform; } core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim) { if (transform % 2 == 0) return dim; return core::dimension2d<u32>(dim.Height, dim.Width); } void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) { if (src == NULL || dst == NULL) return; core::dimension2d<u32> dstdim = dst->getDimension(); // Pre-conditions assert(dstdim == imageTransformDimension(transform, src->getDimension())); assert(transform <= 7); /* Compute the transformation from source coordinates (sx,sy) to destination coordinates (dx,dy). */ int sxn = 0; int syn = 2; if (transform == 0) // identity sxn = 0, syn = 2; // sx = dx, sy = dy else if (transform == 1) // rotate by 90 degrees ccw sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx else if (transform == 2) // rotate by 180 degrees sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy else if (transform == 3) // rotate by 270 degrees ccw sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx else if (transform == 4) // flip x sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy else if (transform == 5) // flip x then rotate by 90 degrees ccw sxn = 2, syn = 0; // sx = dy, sy = dx else if (transform == 6) // flip y sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy else if (transform == 7) // flip y then rotate by 90 degrees ccw sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx for (u32 dy=0; dy<dstdim.Height; dy++) for (u32 dx=0; dx<dstdim.Width; dx++) { u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy}; u32 sx = entries[sxn]; u32 sy = entries[syn]; video::SColor c = src->getPixel(sx,sy); dst->setPixel(dx,dy,c); } } video::ITexture* TextureSource::getNormalTexture(const std::string &name) { if (isKnownSourceImage("override_normal.png")) return getTexture("override_normal.png"); std::string fname_base = name; static const char *normal_ext = "_normal.png"; static const u32 normal_ext_size = strlen(normal_ext); size_t pos = fname_base.find('.'); std::string fname_normal = fname_base.substr(0, pos) + normal_ext; if (isKnownSourceImage(fname_normal)) { // look for image extension and replace it size_t i = 0; while ((i = fname_base.find('.', i)) != std::string::npos) { fname_base.replace(i, 4, normal_ext); i += normal_ext_size; } return getTexture(fname_base); } return NULL; } video::SColor TextureSource::getTextureAverageColor(const std::string &name) { video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::SColor c(0, 0, 0, 0); video::ITexture *texture = getTexture(name); video::IImage *image = driver->createImage(texture, core::position2d<s32>(0, 0), texture->getOriginalSize()); u32 total = 0; u32 tR = 0; u32 tG = 0; u32 tB = 0; core::dimension2d<u32> dim = image->getDimension(); u16 step = 1; if (dim.Width > 16) step = dim.Width / 16; for (u16 x = 0; x < dim.Width; x += step) { for (u16 y = 0; y < dim.Width; y += step) { c = image->getPixel(x,y); if (c.getAlpha() > 0) { total++; tR += c.getRed(); tG += c.getGreen(); tB += c.getBlue(); } } } image->drop(); if (total > 0) { c.setRed(tR / total); c.setGreen(tG / total); c.setBlue(tB / total); } c.setAlpha(255); return c; } video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) { std::string tname = "__shaderFlagsTexture"; tname += normalmap_present ? "1" : "0"; if (isKnownSourceImage(tname)) { return getTexture(tname); } video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::IImage *flags_image = driver->createImage( video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1)); sanity_check(flags_image != NULL); video::SColor c(255, normalmap_present ? 255 : 0, 0, 0); flags_image->setPixel(0, 0, c); insertSourceImage(tname, flags_image); flags_image->drop(); return getTexture(tname); }