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