Line data Source code
1 : /**
2 : * @file texture information class definitions
3 : *
4 : * This file implements a class containing the associated date with a texture image.
5 : * It is only used by texture.cpp and shaderparam.cpp.
6 : */
7 : #include "../libprimis-headers/cube.h"
8 : #include "../../shared/geomexts.h"
9 : #include "../../shared/glexts.h"
10 :
11 : #include "imagedata.h"
12 : #include "renderwindow.h"
13 : #include "shaderparam.h"
14 : #include "texture.h"
15 :
16 : #include "interface/control.h"
17 : #include "interface/console.h"
18 : #include "interface/cs.h" //for stringslice
19 :
20 : namespace
21 : {
22 : //helper function for texturedata
23 0 : vec parsevec(const char *arg)
24 : {
25 0 : vec v(0, 0, 0);
26 0 : uint i = 0;
27 0 : for(; arg[0] && (!i || arg[0]=='/') && i<3; arg += std::strcspn(arg, "/,><"), i++)
28 : {
29 0 : if(i)
30 : {
31 0 : arg++;
32 : }
33 0 : v[i] = std::atof(arg);
34 : }
35 0 : if(i==1)
36 : {
37 0 : v.y = v.z = v.x;
38 : }
39 0 : return v;
40 : }
41 :
42 : //helper function for texturedata
43 0 : bool canloadsurface(const char *name)
44 : {
45 0 : stream *f = openfile(name, "rb");
46 0 : if(!f)
47 : {
48 0 : return false;
49 : }
50 0 : delete f;
51 0 : return true;
52 : }
53 : }
54 :
55 9 : ImageData::ImageData()
56 9 : : data(nullptr), owner(nullptr), freefunc(nullptr)
57 9 : {}
58 :
59 :
60 1 : ImageData::ImageData(int nw, int nh, int nbpp, int nlevels, int nalign, GLenum ncompressed)
61 : {
62 1 : setdata(nullptr, nw, nh, nbpp, nlevels, nalign, ncompressed);
63 1 : }
64 :
65 10 : ImageData::~ImageData()
66 : {
67 10 : cleanup();
68 10 : }
69 :
70 1 : int ImageData::width() const
71 : {
72 1 : return w;
73 : }
74 :
75 2 : int ImageData::height() const
76 : {
77 2 : return h;
78 : }
79 :
80 2 : int ImageData::depth() const
81 : {
82 2 : return bpp;
83 : }
84 :
85 2 : void ImageData::setdata(uchar *ndata, int nw, int nh, int nbpp, int nlevels, int nalign, GLenum ncompressed)
86 : {
87 2 : w = nw;
88 2 : h = nh;
89 2 : bpp = nbpp;
90 2 : levels = nlevels;
91 2 : align = nalign;
92 2 : pitch = align ? 0 : w*bpp;
93 2 : compressed = ncompressed;
94 2 : data = ndata ? ndata : new uchar[calcsize()];
95 2 : if(!ndata)
96 : {
97 1 : owner = this;
98 1 : freefunc = nullptr;
99 : }
100 2 : }
101 :
102 0 : int ImageData::calclevelsize(int level) const
103 : {
104 0 : return ((std::max(w>>level, 1)+align-1)/align)*
105 0 : ((std::max(h>>level, 1)+align-1)/align)*
106 0 : bpp;
107 : }
108 :
109 1 : int ImageData::calcsize() const
110 : {
111 1 : if(!align)
112 : {
113 1 : return w*h*bpp;
114 : }
115 0 : int lw = w,
116 0 : lh = h,
117 0 : size = 0;
118 0 : for(int i = 0; i < levels; ++i)
119 : {
120 0 : if(lw<=0)
121 : {
122 0 : lw = 1;
123 : }
124 0 : if(lh<=0)
125 : {
126 0 : lh = 1;
127 : }
128 0 : size += ((lw+align-1)/align)*((lh+align-1)/align)*bpp;
129 0 : if(lw*lh==1)
130 : {
131 0 : break;
132 : }
133 0 : lw >>= 1;
134 0 : lh >>= 1;
135 : }
136 0 : return size;
137 : }
138 :
139 10 : void ImageData::disown()
140 : {
141 10 : data = nullptr;
142 10 : owner = nullptr;
143 10 : freefunc = nullptr;
144 10 : }
145 :
146 10 : void ImageData::cleanup()
147 : {
148 10 : if(owner==this)
149 : {
150 1 : delete[] data;
151 : }
152 9 : else if(freefunc)
153 : {
154 1 : (*freefunc)(owner);
155 : }
156 10 : disown();
157 10 : }
158 :
159 0 : void ImageData::replace(ImageData &d)
160 : {
161 0 : cleanup();
162 0 : *this = d;
163 0 : if(owner == &d)
164 : {
165 0 : owner = this;
166 : }
167 0 : d.disown();
168 0 : }
169 :
170 1 : void ImageData::wraptex(SDL_Surface *s)
171 : {
172 1 : setdata(static_cast<uchar *>(s->pixels), s->w, s->h, s->format->BytesPerPixel);
173 1 : pitch = s->pitch;
174 1 : owner = s;
175 1 : freefunc = reinterpret_cast<void (*)(void *)>(SDL_FreeSurface);
176 1 : }
177 :
178 : /**
179 : * @brief Manpulates a texture pixel-by-pixel
180 : *
181 : * For each pixel in the source, executes an arbitrary effect, passed as the body
182 : * parameter.
183 : *
184 : * @param t imagedata object, destination of texture write
185 : * @param body code to execute
186 : */
187 : #define WRITE_TEX(t, body) do \
188 : { \
189 : uchar *dstrow = t.data; \
190 : for(int y = 0; y < t.h; ++y) \
191 : { \
192 : for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp]; dst < end; dst += t.bpp) \
193 : { \
194 : body; \
195 : } \
196 : dstrow += t.pitch; \
197 : } \
198 : } while(0)
199 :
200 : /**
201 : * @brief Manpulates one texture using another texture's data.
202 : *
203 : * For each pixel in the source, executes an arbitrary effect, passed as the body
204 : * parameter.
205 : *
206 : * @param t imagedata object, destination of texture write
207 : * @param s imagedata object, source of texture write
208 : * @param body code to execute
209 : */
210 : #define READ_WRITE_TEX(t, s, body) do \
211 : { \
212 : uchar *dstrow = t.data, *srcrow = s.data; \
213 : for(int y = 0; y < t.h; ++y) \
214 : { \
215 : for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; dst += t.bpp, src += s.bpp) \
216 : { \
217 : body; \
218 : } \
219 : dstrow += t.pitch; \
220 : srcrow += s.pitch; \
221 : } \
222 : } while(0)
223 :
224 : #define READ_2_WRITE_TEX(t, s1, src1, s2, src2, body) do \
225 : { \
226 : uchar *dstrow = t.data, *src1row = s1.data, *src2row = s2.data; \
227 : for(int y = 0; y < t.h; ++y) \
228 : { \
229 : for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp], *src1 = src1row, *src2 = src2row; dst < end; dst += t.bpp, src1 += s1.bpp, src2 += s2.bpp) \
230 : { \
231 : body; \
232 : } \
233 : dstrow += t.pitch; \
234 : src1row += s1.pitch; \
235 : src2row += s2.pitch; \
236 : } \
237 : } while(0)
238 :
239 : #define READ_WRITE_RGB_TEX(t, s, body) \
240 : { \
241 : if(t.bpp >= 3) \
242 : { \
243 : READ_WRITE_TEX(t, s, body); \
244 : } \
245 : else \
246 : { \
247 : ImageData rgb(t.w, t.h, 3); \
248 : READ_2_WRITE_TEX(rgb, t, orig, s, src, { dst[0] = dst[1] = dst[2] = orig[0]; body; }); \
249 : t.replace(rgb); \
250 : } \
251 : }
252 :
253 0 : void ImageData::forcergbimage()
254 : {
255 0 : if(bpp >= 3)
256 : {
257 0 : return;
258 : }
259 0 : ImageData d(w, h, 3);
260 0 : READ_WRITE_TEX(d, ((*this)), { dst[0] = dst[1] = dst[2] = src[0]; });
261 0 : replace(d);
262 0 : }
263 :
264 : /**
265 : * @brief Writes data to pixels of an ImageData
266 : *
267 : * Attempts to write to four channels (r,g,b,a) of a texture; if there are at least
268 : * 4 channels already in the destination of the texture operation, uses READ_WRITE_TEX.
269 : *
270 : * @param t imagedata object, destination of texture write
271 : * @param s imagedata object, source of texture data
272 : * @param body code to execute
273 : */
274 : #define READ_WRITE_RGBA_TEX(t, s, body) \
275 : { \
276 : if(t.bpp >= 4) \
277 : { \
278 : READ_WRITE_TEX(t, s, body); \
279 : } \
280 : else \
281 : { \
282 : ImageData rgba(t.w, t.h, 4); \
283 : if(t.bpp==3) \
284 : { \
285 : READ_2_WRITE_TEX(rgba, t, orig, s, src, { dst[0] = orig[0]; dst[1] = orig[1]; dst[2] = orig[2]; body; }); \
286 : } \
287 : else \
288 : { \
289 : READ_2_WRITE_TEX(rgba, t, orig, s, src, { dst[0] = dst[1] = dst[2] = orig[0]; body; }); \
290 : } \
291 : t.replace(rgba); \
292 : } \
293 : }
294 :
295 0 : void ImageData::swizzleimage()
296 : {
297 0 : if(bpp==2)
298 : {
299 0 : ImageData d(w, h, 4);
300 0 : READ_WRITE_TEX(d, (*this), { dst[0] = dst[1] = dst[2] = src[0]; dst[3] = src[1]; });
301 0 : replace(d);
302 0 : }
303 0 : else if(bpp==1)
304 : {
305 0 : ImageData d(w, h, 3);
306 0 : READ_WRITE_TEX(d, (*this), { dst[0] = dst[1] = dst[2] = src[0]; });
307 0 : replace(d);
308 0 : }
309 0 : }
310 :
311 0 : void ImageData::scaleimage(int newwidth, int newheight)
312 : {
313 0 : ImageData d(newwidth, newheight, bpp);
314 0 : scaletexture(data, newwidth, newheight, bpp, pitch, d.data, newwidth, newheight);
315 0 : replace(d);
316 0 : }
317 :
318 0 : void ImageData::texreorient(bool flipx, bool flipy, bool swapxy, int type)
319 : {
320 0 : ImageData d(swapxy ? h : w, swapxy ? w : h, bpp, levels, align, compressed);
321 0 : switch(compressed)
322 : {
323 : default:
324 0 : if(type == Tex_Normal && bpp >= 3)
325 : {
326 0 : reorientnormals(data, w, h, bpp, pitch, d.data, flipx, flipy, swapxy);
327 : }
328 : else
329 : {
330 0 : reorienttexture(data, w, h, bpp, pitch, d.data, flipx, flipy, swapxy);
331 : }
332 0 : break;
333 : }
334 0 : replace(d);
335 0 : }
336 :
337 0 : void ImageData::texrotate(int numrots, int type)
338 : {
339 0 : if(numrots>=1 && numrots<=7)
340 : {
341 0 : const TexRotation &r = texrotations[numrots];
342 0 : texreorient(r.flipx, r.flipy, r.swapxy, type);
343 : }
344 0 : }
345 :
346 0 : void ImageData::texoffset(int xoffset, int yoffset)
347 : {
348 0 : xoffset = std::max(xoffset, 0);
349 0 : xoffset %= w;
350 0 : yoffset = std::max(yoffset, 0);
351 0 : yoffset %= h;
352 0 : if(!xoffset && !yoffset)
353 : {
354 0 : return;
355 : }
356 0 : ImageData d(w, h, bpp);
357 0 : uchar *src = data;
358 0 : for(int y = 0; y < h; ++y)
359 : {
360 0 : uchar *dst = static_cast<uchar *>(d.data)+((y+yoffset)%d.h)*d.pitch;
361 0 : std::memcpy(dst+xoffset*bpp, src, (w-xoffset)*bpp);
362 0 : std::memcpy(dst, src+(w-xoffset)*bpp, xoffset*bpp);
363 0 : src += pitch;
364 : }
365 0 : replace(d);
366 0 : }
367 :
368 0 : void ImageData::texcrop(int x, int y, int w, int h)
369 : {
370 0 : x = std::clamp(x, 0, w);
371 0 : y = std::clamp(y, 0, h);
372 0 : w = std::min(w < 0 ? w : w, w - x);
373 0 : h = std::min(h < 0 ? h : h, h - y);
374 0 : if(!w || !h)
375 : {
376 0 : return;
377 : }
378 0 : ImageData d(w, h, bpp);
379 0 : uchar *src = data + y*pitch + x*bpp,
380 0 : *dst = d.data;
381 0 : for(int y = 0; y < h; ++y)
382 : {
383 0 : std::memcpy(dst, src, w*bpp);
384 0 : src += pitch;
385 0 : dst += d.pitch;
386 : }
387 0 : replace(d);
388 0 : }
389 :
390 0 : void ImageData::texmad(const vec &mul, const vec &add)
391 : {
392 0 : if(bpp < 3 && (mul.x != mul.y || mul.y != mul.z || add.x != add.y || add.y != add.z))
393 : {
394 0 : swizzleimage();
395 : }
396 0 : int maxk = std::min(static_cast<int>(bpp), 3);
397 0 : WRITE_TEX((*this),
398 : for(int k = 0; k < maxk; ++k)
399 : {
400 : dst[k] = static_cast<uchar>(std::clamp(dst[k]*mul[k] + 255*add[k], 0.0f, 255.0f));
401 : }
402 : );
403 0 : }
404 :
405 0 : void ImageData::texcolorify(const vec &color, vec weights)
406 : {
407 0 : if(bpp < 3)
408 : {
409 0 : return;
410 : }
411 0 : if(weights.iszero())
412 : {
413 0 : weights = vec(0.21f, 0.72f, 0.07f);
414 : }
415 0 : WRITE_TEX((*this),
416 : float lum = dst[0]*weights.x + dst[1]*weights.y + dst[2]*weights.z;
417 : for(int k = 0; k < 3; ++k)
418 : {
419 : dst[k] = static_cast<uchar>(std::clamp(lum*color[k], 0.0f, 255.0f));
420 : }
421 : );
422 : }
423 :
424 0 : void ImageData::texcolormask(const vec &color1, const vec &color2)
425 : {
426 0 : if(bpp < 4)
427 : {
428 0 : return;
429 : }
430 0 : ImageData d(w, h, 3);
431 0 : READ_WRITE_TEX(d, (*this),
432 : vec color;
433 : color.lerp(color2, color1, src[3]/255.0f);
434 : for(int k = 0; k < 3; ++k)
435 : {
436 : dst[k] = static_cast<uchar>(std::clamp(color[k]*src[k], 0.0f, 255.0f));
437 : }
438 : );
439 0 : replace(d);
440 0 : }
441 :
442 0 : void ImageData::texdup(int srcchan, int dstchan)
443 : {
444 0 : if(srcchan==dstchan || std::max(srcchan, dstchan) >= bpp)
445 : {
446 0 : return;
447 : }
448 0 : WRITE_TEX((*this), dst[dstchan] = dst[srcchan]);
449 : }
450 :
451 0 : void ImageData::texmix(int c1, int c2, int c3, int c4)
452 : {
453 0 : int numchans = c1 < 0 ? 0 : (c2 < 0 ? 1 : (c3 < 0 ? 2 : (c4 < 0 ? 3 : 4)));
454 0 : if(numchans <= 0)
455 : {
456 0 : return;
457 : }
458 0 : ImageData d(w, h, numchans);
459 0 : READ_WRITE_TEX(d, (*this),
460 : switch(numchans)
461 : {
462 : case 4:
463 : {
464 : dst[3] = src[c4];
465 : break;
466 : }
467 : case 3:
468 : {
469 : dst[2] = src[c3];
470 : break;
471 : }
472 : case 2:
473 : {
474 : dst[1] = src[c2];
475 : break;
476 : }
477 : case 1:
478 : {
479 : dst[0] = src[c1];
480 : break;
481 : }
482 : }
483 : );
484 0 : replace(d);
485 0 : }
486 :
487 0 : void ImageData::texgrey()
488 : {
489 0 : if(bpp <= 2)
490 : {
491 0 : return;
492 : }
493 0 : ImageData d(w, h, bpp >= 4 ? 2 : 1);
494 0 : if(bpp >= 4)
495 : {
496 0 : READ_WRITE_TEX(d, (*this),
497 : dst[0] = src[0];
498 : dst[1] = src[3];
499 : );
500 : }
501 : else
502 : {
503 0 : READ_WRITE_TEX(d, (*this), dst[0] = src[0]);
504 : }
505 0 : replace(d);
506 0 : }
507 :
508 0 : void ImageData::texpremul()
509 : {
510 0 : switch(bpp)
511 : {
512 0 : case 2:
513 0 : WRITE_TEX((*this),
514 : dst[0] = static_cast<uchar>((static_cast<uint>(dst[0])*static_cast<uint>(dst[1]))/255);
515 : );
516 0 : break;
517 0 : case 4:
518 0 : WRITE_TEX((*this),
519 : uint alpha = dst[3];
520 : dst[0] = static_cast<uchar>((static_cast<uint>(dst[0])*alpha)/255);
521 : dst[1] = static_cast<uchar>((static_cast<uint>(dst[1])*alpha)/255);
522 : dst[2] = static_cast<uchar>((static_cast<uint>(dst[2])*alpha)/255);
523 : );
524 0 : break;
525 : }
526 0 : }
527 :
528 0 : void ImageData::texagrad(float x2, float y2, float x1, float y1)
529 : {
530 0 : if(bpp != 2 && bpp != 4)
531 : {
532 0 : return;
533 : }
534 0 : y1 = 1 - y1;
535 0 : y2 = 1 - y2;
536 0 : float minx = 1,
537 0 : miny = 1,
538 0 : maxx = 1,
539 0 : maxy = 1;
540 0 : if(x1 != x2)
541 : {
542 0 : minx = (0 - x1) / (x2 - x1);
543 0 : maxx = (1 - x1) / (x2 - x1);
544 : }
545 0 : if(y1 != y2)
546 : {
547 0 : miny = (0 - y1) / (y2 - y1);
548 0 : maxy = (1 - y1) / (y2 - y1);
549 : }
550 0 : float dx = (maxx - minx)/std::max(w-1, 1),
551 0 : dy = (maxy - miny)/std::max(h-1, 1),
552 0 : cury = miny;
553 0 : for(uchar *dstrow = data + bpp - 1, *endrow = dstrow + h*pitch; dstrow < endrow; dstrow += pitch)
554 : {
555 0 : float curx = minx;
556 0 : for(uchar *dst = dstrow, *end = &dstrow[w*bpp]; dst < end; dst += bpp)
557 : {
558 0 : dst[0] = static_cast<uchar>(dst[0]*std::clamp(curx, 0.0f, 1.0f)*std::clamp(cury, 0.0f, 1.0f));
559 0 : curx += dx;
560 : }
561 0 : cury += dy;
562 : }
563 : }
564 :
565 0 : void ImageData::texblend(const ImageData &s0, const ImageData &m0)
566 : {
567 0 : ImageData s(s0),
568 0 : m(m0);
569 0 : if(s.w != w || s.h != h)
570 : {
571 0 : s.scaleimage(w, h);
572 : }
573 0 : if(m.w != w || m.h != h)
574 : {
575 0 : m.scaleimage(w, h);
576 : }
577 : if(&s == &m)
578 : {
579 : if(s.bpp == 2)
580 : {
581 : if(bpp >= 3)
582 : {
583 : s.swizzleimage();
584 : }
585 : }
586 : else if(s.bpp == 4)
587 : {
588 : if(bpp < 3)
589 : {
590 : swizzleimage();
591 : }
592 : }
593 : else
594 : {
595 : return;
596 : }
597 : //need to declare int for each var because it's inside a macro body
598 : if(bpp < 3)
599 : {
600 : READ_WRITE_TEX((*this), s,
601 : int srcblend = src[1];
602 : int dstblend = 255 - srcblend;
603 : dst[0] = static_cast<uchar>((dst[0]*dstblend + src[0]*srcblend)/255);
604 : );
605 : }
606 : else
607 : {
608 : READ_WRITE_TEX((*this), s,
609 : int srcblend = src[3];
610 : int dstblend = 255 - srcblend;
611 : dst[0] = static_cast<uchar>((dst[0]*dstblend + src[0]*srcblend)/255);
612 : dst[1] = static_cast<uchar>((dst[1]*dstblend + src[1]*srcblend)/255);
613 : dst[2] = static_cast<uchar>((dst[2]*dstblend + src[2]*srcblend)/255);
614 : );
615 : }
616 : }
617 : else
618 : {
619 0 : if(s.bpp < 3)
620 : {
621 0 : if(bpp >= 3)
622 : {
623 0 : s.swizzleimage();
624 : }
625 : }
626 0 : else if(bpp < 3)
627 : {
628 0 : swizzleimage();
629 : }
630 0 : if(bpp < 3)
631 : {
632 0 : READ_2_WRITE_TEX((*this), s, src, m, mask,
633 : int srcblend = mask[0];
634 : int dstblend = 255 - srcblend;
635 : dst[0] = static_cast<uchar>((dst[0]*dstblend + src[0]*srcblend)/255);
636 : );
637 : }
638 : else
639 : {
640 0 : READ_2_WRITE_TEX((*this), s, src, m, mask,
641 : int srcblend = mask[0];
642 : int dstblend = 255 - srcblend;
643 : dst[0] = static_cast<uchar>((dst[0]*dstblend + src[0]*srcblend)/255);
644 : dst[1] = static_cast<uchar>((dst[1]*dstblend + src[1]*srcblend)/255);
645 : dst[2] = static_cast<uchar>((dst[2]*dstblend + src[2]*srcblend)/255);
646 : );
647 : }
648 : }
649 0 : }
650 :
651 0 : void ImageData::addglow(const ImageData &g, const vec &glowcolor)
652 : {
653 0 : if(g.bpp < 3)
654 : {
655 0 : READ_WRITE_RGB_TEX((*this), g,
656 : for(int k = 0; k < 3; ++k)
657 : {
658 : dst[k] = std::clamp(static_cast<int>(dst[k]) + static_cast<int>(src[0]*glowcolor[k]), 0, 255);
659 : }
660 : );
661 : }
662 : else
663 : {
664 0 : READ_WRITE_RGB_TEX((*this), g,
665 : for(int k = 0; k < 3; ++k)
666 : {
667 : dst[k] = std::clamp(static_cast<int>(dst[k]) + static_cast<int>(src[k]*glowcolor[k]), 0, 255);
668 : }
669 : );
670 : }
671 0 : }
672 :
673 0 : void ImageData::mergespec(const ImageData &s)
674 : {
675 0 : if(s.bpp < 3)
676 : {
677 0 : READ_WRITE_RGBA_TEX((*this), s,
678 : dst[3] = src[0];
679 : );
680 : }
681 : else
682 : {
683 0 : READ_WRITE_RGBA_TEX((*this), s,
684 : dst[3] = (static_cast<int>(src[0]) + static_cast<int>(src[1]) + static_cast<int>(src[2]))/3;
685 : );
686 : }
687 0 : }
688 :
689 0 : void ImageData::mergedepth(const ImageData &z)
690 : {
691 0 : READ_WRITE_RGBA_TEX((*this), z,
692 : dst[3] = src[0];
693 : );
694 0 : }
695 :
696 0 : void ImageData::mergealpha(const ImageData &s)
697 : {
698 0 : if(s.bpp < 3)
699 : {
700 0 : READ_WRITE_RGBA_TEX((*this), s,
701 : dst[3] = src[0];
702 : );
703 : }
704 : else
705 : {
706 0 : READ_WRITE_RGBA_TEX((*this), s,
707 : dst[3] = src[3];
708 : );
709 : }
710 0 : }
711 :
712 0 : void ImageData::collapsespec()
713 : {
714 0 : ImageData d(w, h, 1);
715 0 : if(bpp >= 3)
716 : {
717 0 : READ_WRITE_TEX(d, (*this),
718 : {
719 : dst[0] = (static_cast<int>(src[0]) + static_cast<int>(src[1]) + static_cast<int>(src[2]))/3;
720 : });
721 : }
722 : else
723 : {
724 0 : READ_WRITE_TEX(d, (*this), { dst[0] = src[0]; });
725 : }
726 0 : replace(d);
727 0 : }
728 :
729 0 : void ImageData::texnormal(int emphasis)
730 : {
731 0 : ImageData d(w, h, 3);
732 0 : const uchar *src = data;
733 0 : uchar *dst = d.data;
734 0 : for(int y = 0; y < h; ++y)
735 : {
736 0 : for(int x = 0; x < w; ++x)
737 : {
738 0 : vec normal(0.0f, 0.0f, 255.0f/emphasis);
739 0 : normal.x += src[y*pitch + ((x+w-1)%w)*bpp];
740 0 : normal.x -= src[y*pitch + ((x+1)%w)*bpp];
741 0 : normal.y += src[((y+h-1)%h)*pitch + x*bpp];
742 0 : normal.y -= src[((y+1)%h)*pitch + x*bpp];
743 0 : normal.normalize();
744 0 : *(dst++) = static_cast<uchar>(127.5f + normal.x*127.5f);
745 0 : *(dst++) = static_cast<uchar>(127.5f + normal.y*127.5f);
746 0 : *(dst++) = static_cast<uchar>(127.5f + normal.z*127.5f);
747 : }
748 : }
749 0 : replace(d);
750 0 : }
751 :
752 0 : bool ImageData::matchstring(std::string_view s, size_t len, std::string_view d)
753 : {
754 0 : return len == d.size() && !std::memcmp(s.data(), d.data(), d.size());
755 : }
756 :
757 9 : bool ImageData::texturedata(const char *tname, bool msg, int * const compress, int * const wrap, const char *tdir, int ttype)
758 : {
759 0 : auto parsetexcommands = [] (const char *&cmds, const char *&cmd, size_t &len, std::array<const char *, 4> &arg)
760 : {
761 0 : const char *end = nullptr;
762 0 : cmd = &cmds[1];
763 0 : end = std::strchr(cmd, '>');
764 0 : if(!end)
765 : {
766 0 : return true;
767 : }
768 0 : cmds = std::strchr(cmd, '<');
769 0 : len = std::strcspn(cmd, ":,><");
770 0 : for(int i = 0; i < 4; ++i)
771 : {
772 0 : arg[i] = std::strchr(i ? arg[i-1] : cmd, i ? ',' : ':');
773 0 : if(!arg[i] || arg[i] >= end)
774 : {
775 0 : arg[i] = "";
776 : }
777 : else
778 : {
779 0 : arg[i]++;
780 : }
781 : }
782 0 : return false;
783 : };
784 :
785 9 : const char *cmds = nullptr,
786 9 : *file = tname;
787 9 : if(tname[0]=='<')
788 : {
789 0 : cmds = tname;
790 0 : file = std::strrchr(tname, '>');
791 0 : if(!file)
792 : {
793 0 : if(msg)
794 : {
795 0 : conoutf(Console_Error, "could not load <> modified texture %s", tname);
796 : }
797 0 : return false;
798 : }
799 0 : file++;
800 : }
801 : string pname;
802 9 : if(tdir)
803 : {
804 0 : formatstring(pname, "%s/%s", tdir, file);
805 0 : file = path(pname);
806 : }
807 9 : for(const char *pcmds = cmds; pcmds;)
808 : {
809 0 : const char *cmd = nullptr;
810 0 : size_t len = 0;
811 0 : std::array<const char *, 4> arg = { nullptr, nullptr, nullptr, nullptr };
812 :
813 0 : if(parsetexcommands(pcmds, cmd, len, arg))
814 : {
815 0 : break;
816 : }
817 :
818 0 : if(matchstring(cmd, len, "stub"))
819 : {
820 0 : return canloadsurface(file);
821 : }
822 : }
823 9 : if(msg)
824 : {
825 4 : renderprogress(loadprogress, file);
826 : }
827 9 : if(!data)
828 : {
829 9 : SDL_Surface *s = loadsurface(file);
830 9 : if(!s)
831 : {
832 8 : if(msg)
833 : {
834 4 : conoutf(Console_Error, "could not load texture %s", file);
835 : }
836 8 : return false;
837 : }
838 1 : int surfacebpp = s->format->BitsPerPixel;
839 1 : if(surfacebpp%8 || !texformat(surfacebpp/8))
840 : {
841 0 : SDL_FreeSurface(s); conoutf(Console_Error, "texture must be 8, 16, 24, or 32 bpp: %s", file);
842 0 : return false;
843 : }
844 1 : if(std::max(s->w, s->h) > (1<<12))
845 : {
846 0 : SDL_FreeSurface(s); conoutf(Console_Error, "texture size exceeded %dx%d pixels: %s", 1<<12, 1<<12, file);
847 0 : return false;
848 : }
849 1 : wraptex(s);
850 : }
851 :
852 1 : while(cmds)
853 : {
854 0 : const char *cmd = nullptr;
855 0 : size_t len = 0;
856 0 : std::array<const char *, 4> arg = { nullptr, nullptr, nullptr, nullptr };
857 :
858 0 : if(parsetexcommands(cmds, cmd, len, arg))
859 : {
860 0 : break;
861 : }
862 :
863 0 : if(compressed)
864 : {
865 0 : goto compressed; //see `compressed` nested between else & if (yes it's ugly)
866 : }
867 0 : if(matchstring(cmd, len, "mad"))
868 : {
869 0 : texmad(parsevec(arg[0]), parsevec(arg[1]));
870 : }
871 0 : else if(matchstring(cmd, len, "colorify"))
872 : {
873 0 : texcolorify(parsevec(arg[0]), parsevec(arg[1]));
874 : }
875 0 : else if(matchstring(cmd, len, "colormask"))
876 : {
877 0 : texcolormask(parsevec(arg[0]), *arg[1] ? parsevec(arg[1]) : vec(1, 1, 1));
878 : }
879 0 : else if(matchstring(cmd, len, "normal"))
880 : {
881 0 : int emphasis = std::atoi(arg[0]);
882 0 : texnormal(emphasis > 0 ? emphasis : 3);
883 : }
884 0 : else if(matchstring(cmd, len, "dup"))
885 : {
886 0 : texdup(std::atoi(arg[0]), std::atoi(arg[1]));
887 : }
888 0 : else if(matchstring(cmd, len, "offset"))
889 : {
890 0 : texoffset(std::atoi(arg[0]), std::atoi(arg[1]));
891 : }
892 0 : else if(matchstring(cmd, len, "rotate"))
893 : {
894 0 : texrotate(std::atoi(arg[0]), ttype);
895 : }
896 0 : else if(matchstring(cmd, len, "reorient"))
897 : {
898 0 : texreorient(std::atoi(arg[0])>0, std::atoi(arg[1])>0, std::atoi(arg[2])>0, ttype);
899 : }
900 0 : else if(matchstring(cmd, len, "crop"))
901 : {
902 0 : texcrop(std::atoi(arg[0]), std::atoi(arg[1]), *arg[2] ? std::atoi(arg[2]) : -1, *arg[3] ? std::atoi(arg[3]) : -1);
903 : }
904 0 : else if(matchstring(cmd, len, "mix"))
905 : {
906 0 : texmix(*arg[0] ? std::atoi(arg[0]) : -1, *arg[1] ? std::atoi(arg[1]) : -1, *arg[2] ? std::atoi(arg[2]) : -1, *arg[3] ? std::atoi(arg[3]) : -1);
907 : }
908 0 : else if(matchstring(cmd, len, "grey"))
909 : {
910 0 : texgrey();
911 : }
912 0 : else if(matchstring(cmd, len, "premul"))
913 : {
914 0 : texpremul();
915 : }
916 0 : else if(matchstring(cmd, len, "agrad"))
917 : {
918 0 : texagrad(std::atof(arg[0]), std::atof(arg[1]), std::atof(arg[2]), std::atof(arg[3]));
919 : }
920 0 : else if(matchstring(cmd, len, "blend"))
921 : {
922 0 : ImageData src, mask;
923 : string srcname, maskname;
924 0 : copystring(srcname, stringslice(arg[0], std::strcspn(arg[0], ":,><")));
925 0 : copystring(maskname, stringslice(arg[1], std::strcspn(arg[1], ":,><")));
926 0 : if(srcname[0] && src.texturedata(srcname, false, nullptr, nullptr, tdir, ttype) && (!maskname[0] || mask.texturedata(maskname, false, nullptr, nullptr, tdir, ttype)))
927 : {
928 0 : texblend(src, maskname[0] ? mask : src);
929 : }
930 0 : }
931 0 : else if(matchstring(cmd, len, "thumbnail"))
932 : {
933 0 : int xdim = std::atoi(arg[0]),
934 0 : ydim = std::atoi(arg[1]);
935 0 : if(xdim <= 0 || xdim > (1<<12))
936 : {
937 0 : xdim = 64;
938 : }
939 0 : if(ydim <= 0 || ydim > (1<<12))
940 : {
941 0 : ydim = xdim; //default 64
942 : }
943 0 : if(w > xdim || h > ydim)
944 : {
945 0 : scaleimage(xdim, ydim);
946 : }
947 : }
948 0 : else if(matchstring(cmd, len, "compress"))
949 : {
950 0 : int scale = std::atoi(arg[0]);
951 0 : if(compress)
952 : {
953 0 : *compress = scale;
954 : }
955 : }
956 0 : else if(matchstring(cmd, len, "nocompress"))
957 : {
958 0 : if(compress)
959 : {
960 0 : *compress = -1;
961 : }
962 : }
963 : // note that the else/if in else-if is separated by a goto breakpoint
964 : else
965 0 : compressed:
966 0 : if(matchstring(cmd, len, "mirror"))
967 : {
968 0 : if(wrap)
969 : {
970 0 : *wrap |= 0x300;
971 : }
972 : }
973 0 : else if(matchstring(cmd, len, "noswizzle"))
974 : {
975 0 : if(wrap)
976 : {
977 0 : *wrap |= 0x10000;
978 : }
979 : }
980 : }
981 1 : return true;
982 : }
983 :
984 0 : bool ImageData::texturedata(const Slot &slot, const Slot::Tex &tex, bool msg, int *compress, int *wrap)
985 : {
986 0 : return texturedata(tex.name, msg, compress, wrap, slot.texturedir(), tex.type);
987 : }
988 :
989 0 : void ImageData::reorientnormals(uchar * RESTRICT src, int sw, int sh, int surfacebpp, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy)
990 : {
991 0 : int stridex = surfacebpp,
992 0 : stridey = surfacebpp;
993 0 : if(swapxy)
994 : {
995 0 : stridex *= sh;
996 : }
997 : else
998 : {
999 0 : stridey *= sw;
1000 : }
1001 0 : if(flipx)
1002 : {
1003 0 : dst += (sw-1)*stridex;
1004 0 : stridex = -stridex;
1005 : }
1006 0 : if(flipy)
1007 : {
1008 0 : dst += (sh-1)*stridey;
1009 0 : stridey = -stridey;
1010 : }
1011 0 : uchar *srcrow = src;
1012 0 : for(int i = 0; i < sh; ++i)
1013 : {
1014 0 : for(uchar *curdst = dst, *src = srcrow, *end = &srcrow[sw*surfacebpp]; src < end;)
1015 : {
1016 0 : uchar nx = *src++, ny = *src++;
1017 0 : if(flipx)
1018 : {
1019 0 : nx = 255-nx;
1020 : }
1021 0 : if(flipy)
1022 : {
1023 0 : ny = 255-ny;
1024 : }
1025 0 : if(swapxy)
1026 : {
1027 0 : std::swap(nx, ny);
1028 : }
1029 0 : curdst[0] = nx;
1030 0 : curdst[1] = ny;
1031 0 : curdst[2] = *src++;
1032 0 : if(surfacebpp > 3)
1033 : {
1034 0 : curdst[3] = *src++;
1035 : }
1036 0 : curdst += stridex;
1037 : }
1038 0 : srcrow += stride;
1039 0 : dst += stridey;
1040 : }
1041 0 : }
|