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