1 /**\file gl_tex.cpp
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2013 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, see:
17 * http://www.gnu.org/licenses</small>
18 */
19
20 #include "de_platform.h"
21 #include "gl/gl_tex.h"
22 #include "dd_main.h"
23
24 #include "misc/color.h"
25 #include "render/r_main.h"
26 #include "resource/clientresources.h"
27 #include "gl/sys_opengl.h"
28
29 #include <doomsday/resource/colorpalette.h>
30 #include <de/memory.h>
31 #include <de/memoryzone.h>
32 #include <de/vector1.h>
33 #include <de/texgamma.h>
34 #include <cstdlib>
35 #include <cmath>
36 #include <cctype>
37
38 static uint8_t *scratchBuffer;
39 static size_t scratchBufferSize;
40
41 /**
42 * Provides a persistent scratch buffer for use by texture manipulation
43 * routines e.g. scaleLine().
44 */
GetScratchBuffer(size_t size)45 static uint8_t *GetScratchBuffer(size_t size)
46 {
47 // Need to enlarge?
48 if(size > scratchBufferSize)
49 {
50 scratchBuffer = (uint8_t *) Z_Realloc(scratchBuffer, size, PU_APPSTATIC);
51 scratchBufferSize = size;
52 }
53 return scratchBuffer;
54 }
55
56 /**
57 * Len is measured in out units. Comps is the number of components per
58 * pixel, or rather the number of bytes per pixel (3 or 4). The strides must
59 * be byte-aligned anyway, though; not in pixels.
60 *
61 * @todo Probably could be optimized.
62 */
scaleLine(const uint8_t * in,int inStride,uint8_t * out,int outStride,int outLen,int inLen,int comps)63 static void scaleLine(const uint8_t* in, int inStride, uint8_t* out, int outStride,
64 int outLen, int inLen, int comps)
65 {
66 float inToOutScale = outLen / (float) inLen;
67 int i, c;
68
69 if(inToOutScale > 1)
70 {
71 // Magnification is done using linear interpolation.
72 fixed_t inPosDelta = (FRACUNIT * (inLen - 1)) / (outLen - 1);
73 fixed_t inPos = inPosDelta;
74 const uint8_t* col1, *col2;
75 int weight, invWeight;
76
77 // The first pixel.
78 memcpy(out, in, comps);
79 out += outStride;
80
81 // Step at each out pixel between the first and last ones.
82 for(i = 1; i < outLen - 1; ++i, out += outStride, inPos += inPosDelta)
83 {
84 col1 = in + (inPos >> FRACBITS) * inStride;
85 col2 = col1 + inStride;
86 weight = inPos & 0xffff;
87 invWeight = 0x10000 - weight;
88
89 for(c = 0; c < comps; ++c)
90 out[c] = (uint8_t)((col1[c] * invWeight + col2[c] * weight) >> 16);
91 }
92
93 // The last pixel.
94 memcpy(out, in + (inLen - 1) * inStride, comps);
95 return;
96 }
97
98 if(inToOutScale < 1)
99 {
100 // Minification needs to calculate the average of each of
101 // the pixels contained by the out pixel.
102 uint cumul[4] = { 0, 0, 0, 0 }, count = 0;
103 int outpos = 0;
104
105 for(i = 0; i < inLen; ++i, in += inStride)
106 {
107 if((int) (i * inToOutScale) != outpos)
108 {
109 outpos = (int) (i * inToOutScale);
110
111 for(c = 0; c < comps; ++c)
112 {
113 out[c] = (count? uint8_t(cumul[c] / count) : 0);
114 cumul[c] = 0;
115 }
116 count = 0;
117 out += outStride;
118 }
119 for(c = 0; c < comps; ++c)
120 cumul[c] += in[c];
121 count++;
122 }
123 // Fill in the last pixel, too.
124 if(count)
125 for(c = 0; c < comps; ++c)
126 out[c] = (uint8_t)(cumul[c] / count);
127 return;
128 }
129
130 // No need for scaling.
131 for(i = outLen; i > 0; i--, out += outStride, in += inStride)
132 {
133 for(c = 0; c < comps; ++c)
134 out[c] = in[c];
135 }
136 }
137
138 /// \todo Avoid use of a secondary buffer by scaling directly to output.
GL_ScaleBuffer(const uint8_t * in,int width,int height,int comps,int outWidth,int outHeight)139 uint8_t* GL_ScaleBuffer(const uint8_t* in, int width, int height, int comps,
140 int outWidth, int outHeight)
141 {
142 assert(in);
143 {
144 uint inOffsetSize, outOffsetSize;
145 uint8_t* outOff, *buffer;
146 const uint8_t* inOff;
147 uint8_t* out;
148 int stride;
149
150 if(width <= 0 || height <= 0)
151 return (uint8_t*)in;
152
153 buffer = GetScratchBuffer(comps * outWidth * height);
154
155 out = (uint8_t *) M_Malloc(comps * outWidth * outHeight);
156
157 // First scale horizontally, to outWidth, into the temporary buffer.
158 inOff = in;
159 outOff = buffer;
160 inOffsetSize = width * comps;
161 outOffsetSize = outWidth * comps;
162 { int i;
163 for(i = 0; i < height; ++i, inOff += inOffsetSize, outOff += outOffsetSize)
164 {
165 scaleLine(inOff, comps, outOff, comps, outWidth, width, comps);
166 }}
167
168 // Then scale vertically, to outHeight, into the out buffer.
169 inOff = buffer;
170 outOff = out;
171 stride = outWidth * comps;
172 inOffsetSize = comps;
173 outOffsetSize = comps;
174 { int i;
175 for(i = 0; i < outWidth; ++i, inOff += inOffsetSize, outOff += outOffsetSize)
176 {
177 scaleLine(inOff, stride, outOff, stride, outHeight, height, comps);
178 }}
179 return out;
180 }
181 }
182
packImage(int components,const float * tempOut,GLint typeOut,int widthOut,int heightOut,int sizeOut,int bpp,int packRowLength,int packAlignment,int packSkipRows,int packSkipPixels)183 static void* packImage(int components, const float* tempOut, GLint typeOut,
184 int widthOut, int heightOut, int sizeOut, int bpp, int packRowLength,
185 int packAlignment, int packSkipRows, int packSkipPixels)
186 {
187 int rowStride, rowLen;
188 void* dataOut;
189
190 dataOut = M_Malloc(bpp * widthOut * heightOut);
191
192 if(packRowLength > 0)
193 {
194 rowLen = packRowLength;
195 }
196 else
197 {
198 rowLen = widthOut;
199 }
200 if(sizeOut >= packAlignment)
201 {
202 rowStride = components * rowLen;
203 }
204 else
205 {
206 rowStride = packAlignment / sizeOut
207 * CEILING(components * rowLen * sizeOut, packAlignment);
208 }
209
210 switch(typeOut)
211 {
212 case GL_UNSIGNED_BYTE: {
213 int i, j, k = 0;
214 for(i = 0; i < heightOut; ++i)
215 {
216 GLubyte* ubptr = (GLubyte*) dataOut
217 + i * rowStride
218 + packSkipRows * rowStride + packSkipPixels * components;
219 for(j = 0; j < widthOut * components; ++j)
220 {
221 *ubptr++ = (GLubyte) tempOut[k++];
222 }
223 }
224 break;
225 }
226 case GL_BYTE: {
227 int i, j, k = 0;
228 for(i = 0; i < heightOut; i++)
229 {
230 GLbyte* bptr = (GLbyte*) dataOut
231 + i * rowStride
232 + packSkipRows * rowStride + packSkipPixels * components;
233 for(j = 0; j < widthOut * components; ++j)
234 {
235 *bptr++ = (GLbyte) tempOut[k++];
236 }
237 }
238 break;
239 }
240 case GL_UNSIGNED_SHORT: {
241 int i, j, k = 0;
242 for(i = 0; i < heightOut; ++i)
243 {
244 GLushort* usptr = (GLushort*) dataOut
245 + i * rowStride
246 + packSkipRows * rowStride + packSkipPixels * components;
247 for(j = 0; j < widthOut * components; ++j)
248 {
249 *usptr++ = (GLushort) tempOut[k++];
250 }
251 }
252 break;
253 }
254 case GL_SHORT: {
255 int i, j, k = 0;
256 for(i = 0; i < heightOut; ++i)
257 {
258 GLshort* sptr = (GLshort*) dataOut
259 + i * rowStride
260 + packSkipRows * rowStride + packSkipPixels * components;
261 for(j = 0; j < widthOut * components; ++j)
262 {
263 *sptr++ = (GLshort) tempOut[k++];
264 }
265 }
266 break;
267 }
268 case GL_UNSIGNED_INT: {
269 int i, j, k = 0;
270 for(i = 0; i < heightOut; ++i)
271 {
272 GLuint* uiptr = (GLuint*) dataOut
273 + i * rowStride
274 + packSkipRows * rowStride + packSkipPixels * components;
275 for(j = 0; j < widthOut * components; ++j)
276 {
277 *uiptr++ = (GLuint) tempOut[k++];
278 }
279 }
280 break;
281 }
282 case GL_INT: {
283 int i, j, k = 0;
284 for(i = 0; i < heightOut; ++i)
285 {
286 GLint* iptr = (GLint*) dataOut
287 + i * rowStride
288 + packSkipRows * rowStride + packSkipPixels * components;
289 for(j = 0; j < widthOut * components; ++j)
290 {
291 *iptr++ = (GLint) tempOut[k++];
292 }
293 }
294 break;
295 }
296 case GL_FLOAT: {
297 int i, j, k = 0;
298 for(i = 0; i < heightOut; ++i)
299 {
300 GLfloat* fptr = (GLfloat*) dataOut
301 + i * rowStride
302 + packSkipRows * rowStride + packSkipPixels * components;
303 for (j = 0; j < widthOut * components; ++j)
304 {
305 *fptr++ = tempOut[k++];
306 }
307 }
308 break;
309 }
310 default:
311 DENG_ASSERT(!"packImage: Unknown output type");
312 return 0;
313 }
314
315 return dataOut;
316 }
317
318 /**
319 * Originally from the Mesa 3-D graphics library version 3.4
320 * @note License: GNU Library General Public License (or later)
321 * Copyright (C) 1995-2000 Brian Paul.
322 */
GL_ScaleBufferEx(const void * dataIn,int widthIn,int heightIn,int bpp,int unpackRowLength,int unpackAlignment,int unpackSkipRows,int unpackSkipPixels,int widthOut,int heightOut,int packRowLength,int packAlignment,int packSkipRows,int packSkipPixels)323 void* GL_ScaleBufferEx(const void* dataIn, int widthIn, int heightIn, int bpp,
324 /*GLint typeIn,*/ int unpackRowLength, int unpackAlignment, int unpackSkipRows,
325 int unpackSkipPixels, int widthOut, int heightOut, /*GLint typeOut, */
326 int packRowLength, int packAlignment, int packSkipRows, int packSkipPixels)
327 {
328 const GLint typeIn = GL_UNSIGNED_BYTE, typeOut = GL_UNSIGNED_BYTE;
329 int i, j, k, sizeIn, sizeOut, rowStride, rowLen;
330 float* tempIn, *tempOut;
331 float sx, sy;
332 void* dataOut;
333
334 // Determine bytes per input datum.
335 switch(typeIn)
336 {
337 case GL_UNSIGNED_BYTE:
338 sizeIn = sizeof(GLubyte);
339 break;
340 case GL_BYTE:
341 sizeIn = sizeof(GLbyte);
342 break;
343 case GL_UNSIGNED_SHORT:
344 sizeIn = sizeof(GLushort);
345 break;
346 case GL_SHORT:
347 sizeIn = sizeof(GLshort);
348 break;
349 case GL_UNSIGNED_INT:
350 sizeIn = sizeof(GLuint);
351 break;
352 case GL_INT:
353 sizeIn = sizeof(GLint);
354 break;
355 case GL_FLOAT:
356 sizeIn = sizeof(GLfloat);
357 break;
358 default:
359 return NULL;
360 }
361
362 // Determine bytes per output datum.
363 switch(typeOut)
364 {
365 case GL_UNSIGNED_BYTE:
366 sizeOut = sizeof(GLubyte);
367 break;
368 case GL_BYTE:
369 sizeOut = sizeof(GLbyte);
370 break;
371 case GL_UNSIGNED_SHORT:
372 sizeOut = sizeof(GLushort);
373 break;
374 case GL_SHORT:
375 sizeOut = sizeof(GLshort);
376 break;
377 case GL_UNSIGNED_INT:
378 sizeOut = sizeof(GLuint);
379 break;
380 case GL_INT:
381 sizeOut = sizeof(GLint);
382 break;
383 case GL_FLOAT:
384 sizeOut = sizeof(GLfloat);
385 break;
386 default:
387 return NULL;
388 }
389
390 // Allocate storage for intermediate images.
391 tempIn = (float *) M_Malloc(widthIn * heightIn * bpp * sizeof(float));
392
393 tempOut = (float *) M_Malloc(widthOut * heightOut * bpp * sizeof(float));
394
395 /**
396 * Unpack the pixel data and convert to floating point
397 */
398
399 if(unpackRowLength > 0)
400 {
401 rowLen = unpackRowLength;
402 }
403 else
404 {
405 rowLen = widthIn;
406 }
407
408 if(sizeIn >= unpackAlignment)
409 {
410 rowStride = bpp * rowLen;
411 }
412 else
413 {
414 rowStride = unpackAlignment / sizeIn
415 * CEILING(bpp * rowLen * sizeIn, unpackAlignment);
416 }
417
418 switch(typeIn)
419 {
420 case GL_UNSIGNED_BYTE:
421 k = 0;
422 for(i = 0; i < heightIn; ++i)
423 {
424 GLubyte* ubptr = (GLubyte*) dataIn
425 + i * rowStride
426 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
427 for(j = 0; j < widthIn * bpp; ++j)
428 {
429 tempIn[k++] = (float) *ubptr++;
430 }
431 }
432 break;
433 case GL_BYTE:
434 k = 0;
435 for(i = 0; i < heightIn; ++i)
436 {
437 GLbyte* bptr = (GLbyte*) dataIn
438 + i * rowStride
439 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
440 for(j = 0; j < widthIn * bpp; ++j)
441 {
442 tempIn[k++] = (float) *bptr++;
443 }
444 }
445 break;
446 case GL_UNSIGNED_SHORT:
447 k = 0;
448 for(i = 0; i < heightIn; ++i)
449 {
450 GLushort* usptr = (GLushort*) dataIn
451 + i * rowStride
452 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
453 for(j = 0; j < widthIn * bpp; ++j)
454 {
455 tempIn[k++] = (float) *usptr++;
456 }
457 }
458 break;
459 case GL_SHORT:
460 k = 0;
461 for(i = 0; i < heightIn; ++i)
462 {
463 GLshort* sptr = (GLshort*) dataIn
464 + i * rowStride
465 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
466 for(j = 0; j < widthIn * bpp; ++j)
467 {
468 tempIn[k++] = (float) *sptr++;
469 }
470 }
471 break;
472 case GL_UNSIGNED_INT:
473 k = 0;
474 for(i = 0; i < heightIn; ++i)
475 {
476 GLuint* uiptr = (GLuint*) dataIn
477 + i * rowStride
478 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
479 for(j = 0; j < widthIn * bpp; ++j)
480 {
481 tempIn[k++] = (float) *uiptr++;
482 }
483 }
484 break;
485 case GL_INT:
486 k = 0;
487 for(i = 0; i < heightIn; ++i)
488 {
489 GLint* iptr = (GLint*) dataIn
490 + i * rowStride
491 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
492 for(j = 0; j < widthIn * bpp; ++j)
493 {
494 tempIn[k++] = (float) *iptr++;
495 }
496 }
497 break;
498 case GL_FLOAT:
499 k = 0;
500 for(i = 0; i < heightIn; ++i)
501 {
502 GLfloat* fptr = (GLfloat*) dataIn
503 + i * rowStride
504 + unpackSkipRows * rowStride + unpackSkipPixels * bpp;
505 for(j = 0; j < widthIn * bpp; ++j)
506 {
507 tempIn[k++] = *fptr++;
508 }
509 }
510 break;
511 default:
512 return 0;
513 }
514
515 /**
516 * Scale the image!
517 */
518
519 if(widthOut > 1)
520 sx = (float) (widthIn - 1) / (float) (widthOut - 1);
521 else
522 sx = (float) (widthIn - 1);
523 if(heightOut > 1)
524 sy = (float) (heightIn - 1) / (float) (heightOut - 1);
525 else
526 sy = (float) (heightIn - 1);
527
528 if(sx < 1.0 && sy < 1.0)
529 {
530 // Magnify both width and height: use weighted sample of 4 pixels.
531 int i0, i1, j0, j1;
532 float alpha, beta;
533 float* src00, *src01, *src10, *src11;
534 float s1, s2;
535 float* dst;
536
537 for(i = 0; i < heightOut; ++i)
538 {
539 i0 = i * sy;
540 i1 = i0 + 1;
541 if(i1 >= heightIn)
542 i1 = heightIn - 1;
543 alpha = i * sy - i0;
544 for(j = 0; j < widthOut; ++j)
545 {
546 j0 = j * sx;
547 j1 = j0 + 1;
548 if(j1 >= widthIn)
549 j1 = widthIn - 1;
550 beta = j * sx - j0;
551
552 // Compute weighted average of pixels in rect (i0,j0)-(i1,j1)
553 src00 = tempIn + (i0 * widthIn + j0) * bpp;
554 src01 = tempIn + (i0 * widthIn + j1) * bpp;
555 src10 = tempIn + (i1 * widthIn + j0) * bpp;
556 src11 = tempIn + (i1 * widthIn + j1) * bpp;
557
558 dst = tempOut + (i * widthOut + j) * bpp;
559
560 for (k = 0; k < bpp; ++k)
561 {
562 s1 = *src00++ * (1.0 - beta) + *src01++ * beta;
563 s2 = *src10++ * (1.0 - beta) + *src11++ * beta;
564 *dst++ = s1 * (1.0 - alpha) + s2 * alpha;
565 }
566 }
567 }
568 }
569 else
570 {
571 // Shrink width and/or height: use an unweighted box filter.
572 int i0, i1;
573 int j0, j1;
574 int ii, jj;
575 float sum, *dst;
576
577 for(i = 0; i < heightOut; ++i)
578 {
579 i0 = i * sy;
580 i1 = i0 + 1;
581 if(i1 >= heightIn)
582 i1 = heightIn - 1;
583
584 for(j = 0; j < widthOut; ++j)
585 {
586 j0 = j * sx;
587 j1 = j0 + 1;
588 if(j1 >= widthIn)
589 j1 = widthIn - 1;
590
591 dst = tempOut + (i * widthOut + j) * bpp;
592
593 // Compute average of pixels in the rectangle (i0,j0)-(i1,j1)
594 for(k = 0; k < bpp; ++k)
595 {
596 sum = 0.0;
597 for(ii = i0; ii <= i1; ++ii)
598 {
599 for(jj = j0; jj <= j1; ++jj)
600 {
601 sum += *(tempIn + (ii * widthIn + jj) * bpp + k);
602 }
603 }
604 sum /= (j1 - j0 + 1) * (i1 - i0 + 1);
605 *dst++ = sum;
606 }
607 }
608 }
609 }
610
611 // Free temporary image storage.
612 free(tempIn);
613
614 /**
615 * Return output image.
616 */
617 dataOut = packImage(bpp, tempOut, typeOut, widthOut, heightOut, sizeOut, bpp,
618 packRowLength, packAlignment, packSkipRows, packSkipPixels);
619
620 // Free temporary image storage.
621 free(tempOut);
622
623 return dataOut;
624 }
625
GL_ScaleBufferNearest(const uint8_t * in,int width,int height,int comps,int outWidth,int outHeight)626 uint8_t* GL_ScaleBufferNearest(const uint8_t* in, int width, int height, int comps,
627 int outWidth, int outHeight)
628 {
629 assert(in);
630 {
631 int ratioX, ratioY, shearY;
632 uint8_t* out, *outP;
633
634 if(width <= 0 || height <= 0)
635 return (uint8_t*)in;
636
637 ratioX = (int)(width << 16) / outWidth + 1;
638 ratioY = (int)(height << 16) / outHeight + 1;
639
640 out = (uint8_t *) M_Malloc(comps * outWidth * outHeight);
641
642 outP = out;
643 shearY = 0;
644 { int i;
645 for(i = 0; i < outHeight; ++i, shearY += ratioY)
646 {
647 int shearX = 0;
648 int shearY2 = (shearY >> 16) * width;
649 { int j;
650 for(j = 0; j < outWidth; ++j, outP += comps, shearX += ratioX)
651 {
652 int c, n = (shearY2 + (shearX >> 16)) * comps;
653 for(c = 0; c < comps; ++c, n++)
654 outP[c] = in[n];
655 }}
656 }}
657 return out;
658 }
659 }
660
GL_DownMipmap32(uint8_t * in,int width,int height,int comps)661 void GL_DownMipmap32(uint8_t* in, int width, int height, int comps)
662 {
663 assert(in);
664 {
665 int x, y, c, outW = width >> 1, outH = height >> 1;
666 uint8_t* out;
667
668 if(width <= 0 || height <= 0 || comps <= 0)
669 return;
670
671 if(width == 1 && height == 1)
672 {
673 DENG_ASSERT(!"GL_DownMipmap32: Can't be called for a 1x1 image.");
674 return;
675 }
676
677 // Limited, 1x2|2x1 -> 1x1 reduction?
678 if(!outW || !outH)
679 {
680 int outDim = (width > 1 ? outW : outH);
681
682 out = in;
683 for(x = 0; x < outDim; ++x, in += comps * 2)
684 for(c = 0; c < comps; ++c, out++)
685 *out = (uint8_t)((in[c] + in[comps + c]) >> 1);
686 return;
687 }
688
689 // Unconstrained, 2x2 -> 1x1 reduction?
690 out = in;
691 for(y = 0; y < outH; ++y, in += width * comps)
692 for(x = 0; x < outW; ++x, in += comps * 2)
693 for(c = 0; c < comps; ++c, out++)
694 *out = (uint8_t)((in[c] + in[comps + c] + in[comps * width + c] +
695 in[comps * (width + 1) + c]) >> 2);
696 }
697 }
698
GL_DownMipmap8(uint8_t * in,uint8_t * fadedOut,int width,int height,float fade)699 void GL_DownMipmap8(uint8_t* in, uint8_t* fadedOut, int width, int height, float fade)
700 {
701 int x, y, outW = width / 2, outH = height / 2;
702 float invFade;
703 byte* out = in;
704
705 if(fade > 1)
706 fade = 1;
707 invFade = 1 - fade;
708
709 if(width == 1 && height == 1)
710 {
711 DENG_ASSERT(!"GL_DownMipmap8: Can't be called for a 1x1 image.");
712 return;
713 }
714
715 if(!outW || !outH)
716 { // Limited, 1x2|2x1 -> 1x1 reduction?
717 int outDim = (width > 1 ? outW : outH);
718
719 for(x = 0; x < outDim; x++, in += 2)
720 {
721 *out = (in[0] + in[1]) / 2;
722 *fadedOut++ = (byte) (*out * invFade + 0x80 * fade);
723 out++;
724 }
725 }
726 else
727 { // Unconstrained, 2x2 -> 1x1 reduction?
728 for(y = 0; y < outH; y++, in += width)
729 for(x = 0; x < outW; x++, in += 2)
730 {
731 *out = (in[0] + in[1] + in[width] + in[width + 1]) / 4;
732 *fadedOut++ = (byte) (*out * invFade + 0x80 * fade);
733 out++;
734 }
735 }
736 }
737
GL_PalettizeImage(uint8_t * out,int outformat,res::ColorPalette const * palette,dd_bool applyTexGamma,uint8_t const * in,int informat,int width,int height)738 dd_bool GL_PalettizeImage(uint8_t *out, int outformat, res::ColorPalette const *palette,
739 dd_bool applyTexGamma, uint8_t const *in, int informat, int width, int height)
740 {
741 DENG2_ASSERT(in && out && palette);
742
743 if(width <= 0 || height <= 0)
744 return false;
745
746 if(informat <= 2 && outformat >= 3)
747 {
748 long const numPels = width * height;
749 int const inSize = (informat == 2 ? 1 : informat);
750 int const outSize = (outformat == 2 ? 1 : outformat);
751
752 for(long i = 0; i < numPels; ++i)
753 {
754 de::Vector3ub palColor = palette->color(*in);
755
756 out[0] = palColor.x;
757 out[1] = palColor.y;
758 out[2] = palColor.z;
759
760 if(applyTexGamma)
761 {
762 out[0] = R_TexGammaLut(out[0]);
763 out[1] = R_TexGammaLut(out[1]);
764 out[2] = R_TexGammaLut(out[2]);
765 }
766
767 if(outformat == 4)
768 {
769 if(informat == 2)
770 out[3] = in[numPels * inSize];
771 else
772 out[3] = 0;
773 }
774
775 in += inSize;
776 out += outSize;
777 }
778 return true;
779 }
780 return false;
781 }
782
GL_QuantizeImageToPalette(uint8_t * out,int outformat,res::ColorPalette const * palette,uint8_t const * in,int informat,int width,int height)783 dd_bool GL_QuantizeImageToPalette(uint8_t *out, int outformat, res::ColorPalette const *palette,
784 uint8_t const *in, int informat, int width, int height)
785 {
786 DENG2_ASSERT(out != 0 && in != 0 && palette != 0);
787
788 if(informat >= 3 && outformat <= 2 && width > 0 && height > 0)
789 {
790 int inSize = (informat == 2 ? 1 : informat);
791 int outSize = (outformat == 2 ? 1 : outformat);
792 int i, numPixels = width * height;
793
794 for(i = 0; i < numPixels; ++i, in += inSize, out += outSize)
795 {
796 // Convert the color value.
797 *out = palette->nearestIndex(de::Vector3ub(in));
798
799 // Alpha channel?
800 if(outformat == 2)
801 {
802 if(informat == 4)
803 out[numPixels * outSize] = in[3];
804 else
805 out[numPixels * outSize] = 0;
806 }
807 }
808 return true;
809 }
810 return false;
811 }
812
GL_DeSaturatePalettedImage(uint8_t * pixels,res::ColorPalette const & palette,int width,int height)813 void GL_DeSaturatePalettedImage(uint8_t *pixels, res::ColorPalette const &palette,
814 int width, int height)
815 {
816 DENG2_ASSERT(pixels != 0);
817
818 if(!width || !height) return;
819
820 long const numPels = width * height;
821
822 // What is the maximum color value?
823 int max = 0;
824 for(long i = 0; i < numPels; ++i)
825 {
826 de::Vector3ub palColor = palette[pixels[i]];
827 if(palColor.x == palColor.y && palColor.x == palColor.z)
828 {
829 if(palColor.x > max)
830 {
831 max = palColor.x;
832 }
833 continue;
834 }
835
836 int temp = (2 * int( palColor.x ) + 4 * int( palColor.y ) + 3 * int( palColor.z )) / 9;
837 if(temp > max) max = temp;
838 }
839
840 for(long i = 0; i < numPels; ++i)
841 {
842 de::Vector3ub palColor = palette[pixels[i]];
843 if(palColor.x == palColor.y && palColor.x == palColor.z)
844 {
845 continue;
846 }
847
848 // Calculate a weighted average.
849 int temp = (2 * int( palColor.x ) + 4 * int( palColor.y ) + 3 * int( palColor.z )) / 9;
850 if(max) temp *= 255.f / max;
851
852 pixels[i] = palette.nearestIndex(de::Vector3ub(temp, temp, temp));
853 }
854 }
855
FindAverageLineColorIdx(uint8_t const * data,int w,int h,int line,res::ColorPalette const & palette,dd_bool hasAlpha,ColorRawf * color)856 void FindAverageLineColorIdx(uint8_t const *data, int w, int h, int line,
857 res::ColorPalette const &palette, dd_bool hasAlpha, ColorRawf *color)
858 {
859 DENG2_ASSERT(data != 0 && color != 0);
860
861 long i, count, numpels, avg[3] = { 0, 0, 0 };
862 uint8_t const *start, *alphaStart;
863
864 if(w <= 0 || h <= 0)
865 {
866 V3f_Set(color->rgb, 0, 0, 0);
867 return;
868 }
869
870 if(line >= h)
871 {
872 App_Log(DE2_DEV_GL_ERROR, "FindAverageLineColorIdx: height=%i, line=%i.", h, line);
873 DENG_ASSERT(!"FindAverageLineColorIdx: Attempted to average outside valid area.");
874 V3f_Set(color->rgb, 0, 0, 0);
875 return;
876 }
877
878 numpels = w * h;
879 start = data + w * line;
880 alphaStart = data + numpels + w * line;
881 count = 0;
882 for(i = 0; i < w; ++i)
883 {
884 if(!hasAlpha || alphaStart[i])
885 {
886 de::Vector3ub palColor = palette[start[i]];
887 avg[0] += palColor.x;
888 avg[1] += palColor.y;
889 avg[2] += palColor.z;
890 ++count;
891 }
892 }
893
894 // All transparent? Sorry...
895 if(!count) return;
896
897 V3f_Set(color->rgb, avg[0] / count * reciprocal255,
898 avg[1] / count * reciprocal255,
899 avg[2] / count * reciprocal255);
900 }
901
FindAverageLineColor(const uint8_t * pixels,int width,int height,int pixelSize,int line,ColorRawf * color)902 void FindAverageLineColor(const uint8_t* pixels, int width, int height,
903 int pixelSize, int line, ColorRawf* color)
904 {
905 long avg[3] = { 0, 0, 0 };
906 const uint8_t* src;
907 int i;
908 assert(pixels && color);
909
910 if(width <= 0 || height <= 0)
911 {
912 V3f_Set(color->rgb, 0, 0, 0);
913 return;
914 }
915
916 if(line >= height)
917 {
918 App_Log(DE2_DEV_GL_ERROR, "EnhanceContrast: height=%i, line=%i.", height, line);
919 DENG_ASSERT(!"FindAverageLineColor: Attempted to average outside valid area.");
920
921 V3f_Set(color->rgb, 0, 0, 0);
922 return;
923 }
924
925 src = pixels + pixelSize * width * line;
926 for(i = 0; i < width; ++i, src += pixelSize)
927 {
928 avg[0] += src[0];
929 avg[1] += src[1];
930 avg[2] += src[2];
931 }
932
933 V3f_Set(color->rgb, avg[0] / width * reciprocal255,
934 avg[1] / width * reciprocal255,
935 avg[2] / width * reciprocal255);
936 }
937
FindAverageColor(const uint8_t * pixels,int width,int height,int pixelSize,ColorRawf * color)938 void FindAverageColor(const uint8_t* pixels, int width, int height,
939 int pixelSize, ColorRawf* color)
940 {
941 long i, numpels, avg[3] = { 0, 0, 0 };
942 const uint8_t* src;
943 assert(pixels && color);
944
945 if(width <= 0 || height <= 0)
946 {
947 V3f_Set(color->rgb, 0, 0, 0);
948 return;
949 }
950
951 if(pixelSize != 3 && pixelSize != 4)
952 {
953 App_Log(DE2_DEV_GL_ERROR, "FindAverageColor: pixelSize=%i", pixelSize);
954 DENG_ASSERT("FindAverageColor: Attempted on non-rgb(a) image.");
955
956 V3f_Set(color->rgb, 0, 0, 0);
957 return;
958 }
959
960 numpels = width * height;
961 src = pixels;
962 for(i = 0; i < numpels; ++i, src += pixelSize)
963 {
964 avg[0] += src[0];
965 avg[1] += src[1];
966 avg[2] += src[2];
967 }
968
969 V3f_Set(color->rgb, avg[0] / numpels * reciprocal255,
970 avg[1] / numpels * reciprocal255,
971 avg[2] / numpels * reciprocal255);
972 }
973
FindAverageColorIdx(uint8_t const * data,int w,int h,res::ColorPalette const & palette,dd_bool hasAlpha,ColorRawf * color)974 void FindAverageColorIdx(uint8_t const *data, int w, int h, res::ColorPalette const &palette,
975 dd_bool hasAlpha, ColorRawf *color)
976 {
977 DENG2_ASSERT(data != 0 && color != 0);
978
979 long i, numpels, count, avg[3] = { 0, 0, 0 };
980 uint8_t const *alphaStart;
981
982 if(w <= 0 || h <= 0)
983 {
984 V3f_Set(color->rgb, 0, 0, 0);
985 return;
986 }
987
988 numpels = w * h;
989 alphaStart = data + numpels;
990 count = 0;
991 for(i = 0; i < numpels; ++i)
992 {
993 if(!hasAlpha || alphaStart[i])
994 {
995 de::Vector3ub palColor = palette[data[i]];
996 avg[0] += palColor.x;
997 avg[1] += palColor.y;
998 avg[2] += palColor.z;
999 ++count;
1000 }
1001 }
1002
1003 // All transparent? Sorry...
1004 if(0 == count) return;
1005
1006 V3f_Set(color->rgb, avg[0] / count * reciprocal255,
1007 avg[1] / count * reciprocal255,
1008 avg[2] / count * reciprocal255);
1009 }
1010
FindAverageAlpha(const uint8_t * pixels,int width,int height,int pixelSize,float * alpha,float * coverage)1011 void FindAverageAlpha(const uint8_t* pixels, int width, int height,
1012 int pixelSize, float* alpha, float* coverage)
1013 {
1014 long i, numPels, avg = 0, alphaCount = 0;
1015 const uint8_t* src;
1016
1017 if(!pixels || !alpha) return;
1018
1019 if(width <= 0 || height <= 0)
1020 {
1021 // Transparent.
1022 *alpha = 0;
1023 if(coverage) *coverage = 1;
1024 return;
1025 }
1026
1027 if(pixelSize != 3 && pixelSize != 4)
1028 {
1029 App_Log(DE2_DEV_GL_ERROR, "FindAverageAlpha: pixelSize=%i", pixelSize);
1030 DENG_ASSERT(!"FindAverageAlpha: Attempted on non-rgb(a) image.");
1031
1032 // Assume opaque.
1033 *alpha = 1;
1034 if(coverage) *coverage = 0;
1035 return;
1036 }
1037
1038 if(pixelSize == 3)
1039 {
1040 // Opaque. Well that was easy...
1041 *alpha = 1;
1042 if(coverage) *coverage = 0;
1043 return;
1044 }
1045
1046 numPels = width * height;
1047 src = pixels;
1048 for(i = 0; i < numPels; ++i, src += 4)
1049 {
1050 const uint8_t val = src[3];
1051 avg += val;
1052 if(val < 255) alphaCount++;
1053 }
1054
1055 *alpha = avg / numPels * reciprocal255;
1056
1057 // Calculate coverage?
1058 if(coverage) *coverage = (float)alphaCount / numPels;
1059 }
1060
FindAverageAlphaIdx(uint8_t const * pixels,int w,int h,float * alpha,float * coverage)1061 void FindAverageAlphaIdx(uint8_t const *pixels, int w, int h, float *alpha,
1062 float *coverage)
1063 {
1064 long i, numPels, avg = 0, alphaCount = 0;
1065 uint8_t const *alphaStart;
1066
1067 if(!pixels || !alpha) return;
1068
1069 if(w <= 0 || h <= 0)
1070 {
1071 // Transparent.
1072 *alpha = 0;
1073 if(coverage) *coverage = 1;
1074 return;
1075 }
1076
1077 numPels = w * h;
1078 alphaStart = pixels + numPels;
1079 for(i = 0; i < numPels; ++i)
1080 {
1081 uint8_t const val = alphaStart[i];
1082 avg += val;
1083 if(val < 255)
1084 {
1085 alphaCount++;
1086 }
1087 }
1088
1089 *alpha = avg / numPels * reciprocal255;
1090
1091 // Calculate coverage?
1092 if(coverage) *coverage = (float)alphaCount / numPels;
1093 }
1094
FindClipRegionNonAlpha(const uint8_t * buffer,int width,int height,int pixelsize,int retRegion[4])1095 void FindClipRegionNonAlpha(const uint8_t* buffer, int width, int height,
1096 int pixelsize, int retRegion[4])
1097 {
1098 assert(buffer && retRegion);
1099 {
1100 const uint8_t* src, *alphasrc;
1101 int region[4];
1102
1103 if(width <= 0 || height <= 0)
1104 {
1105 DENG_ASSERT(!"FindClipRegionNonAlpha: Attempt to find region on zero-sized image.");
1106
1107 retRegion[0] = retRegion[1] = retRegion[2] = retRegion[3] = 0;
1108 return;
1109 }
1110
1111 region[0] = width;
1112 region[1] = 0;
1113 region[2] = height;
1114 region[3] = 0;
1115
1116 src = buffer;
1117 // For paletted images the alpha channel follows the actual image.
1118 if(pixelsize == 1)
1119 alphasrc = buffer + width * height;
1120 else
1121 alphasrc = NULL;
1122
1123 // \todo This is not very efficent. Better to use an algorithm which works
1124 // on full rows and full columns.
1125 { int k, i;
1126 for(k = 0; k < height; ++k)
1127 for(i = 0; i < width; ++i, src += pixelsize, alphasrc++)
1128 {
1129 // Alpha pixels don't count.
1130 if(pixelsize == 1)
1131 {
1132 if(*alphasrc < 255)
1133 continue;
1134 }
1135 else if(pixelsize == 4)
1136 {
1137 if(src[3] < 255)
1138 continue;
1139 }
1140
1141 if(i < region[0])
1142 region[0] = i;
1143 if(i > region[1])
1144 region[1] = i;
1145
1146 if(k < region[2])
1147 region[2] = k;
1148 if(k > region[3])
1149 region[3] = k;
1150 }
1151 }
1152
1153 retRegion[0] = region[0];
1154 retRegion[1] = region[1];
1155 retRegion[2] = region[2];
1156 retRegion[3] = region[3];
1157 }
1158 }
1159
1160 #if 0
1161 void BlackOutlines(uint8_t* pixels, int width, int height)
1162 {
1163 assert(pixels);
1164 {
1165 uint16_t* dark;
1166 int x, y, a, b;
1167 uint8_t* pix;
1168 long numpels;
1169
1170 if(width <= 0 || height <= 0)
1171 return;
1172
1173 numpels = width * height;
1174 dark = calloc(1, 2 * numpels);
1175
1176 for(y = 1; y < height - 1; ++y)
1177 {
1178 for(x = 1; x < width - 1; ++x)
1179 {
1180 pix = pixels + (x + y * width) * 4;
1181 if(pix[3] > 128) // Not transparent.
1182 {
1183 // Apply darkness to surrounding transparent pixels.
1184 for(a = -1; a <= 1; ++a)
1185 {
1186 for(b = -1; b <= 1; ++b)
1187 {
1188 uint8_t* other = pix + (a + b * width) * 4;
1189 if(other[3] < 128) // Transparent.
1190 {
1191 dark[(x + a) + (y + b) * width] += 40;
1192 }
1193 }
1194 }
1195 }
1196 }
1197 }
1198
1199 // Apply the darkness.
1200 { long i;
1201 for(i = 0, pix = pixels; i < numpels; ++i, pix += 4)
1202 {
1203 pix[3] = MIN_OF(255, (int)(pix[3] + dark[a]));
1204 }}
1205
1206 // Release temporary storage.
1207 free(dark);
1208 }
1209 }
1210 #endif
1211
ColorOutlinesIdx(uint8_t * buffer,int width,int height)1212 void ColorOutlinesIdx(uint8_t* buffer, int width, int height)
1213 {
1214 DENG_ASSERT(buffer);
1215
1216 const int numpels = width * height;
1217 uint8_t* w[5];
1218 int x, y;
1219
1220 if(width <= 0 || height <= 0)
1221 return;
1222
1223 // +----+
1224 // | w0 |
1225 // +----+----+----+
1226 // | w1 | w2 | w3 |
1227 // +----+----+----+
1228 // | w4 |
1229 // +----+
1230
1231 /// @todo Not a very efficient algorithm...
1232
1233 for(y = 0; y < height; ++y)
1234 {
1235 for(x = 0; x < width; ++x)
1236 {
1237 // Only solid pixels spread.
1238 if(!buffer[numpels + x + y * width])
1239 continue;
1240
1241 w[2] = buffer + x + y * width;
1242
1243 w[0] = buffer + x + (y + (y == 0? 0 : -1)) * width;
1244 w[4] = buffer + x + (y + (y == height-1? 0 : 1)) * width;
1245
1246 w[1] = buffer + x + (x == 0? 0 : -1) + (y) * width;
1247 w[3] = buffer + x + (x == width-1? 0 : 1) + (y) * width;
1248
1249 if(w[0] != w[2] && !(*(w[0]+numpels)))
1250 *(w[0]) = *(w[2]);
1251
1252 if(w[4] != w[2] && !(*(w[4]+numpels)))
1253 *(w[4]) = *(w[2]);
1254
1255 if(w[1] != w[2] && !(*(w[1]+numpels)))
1256 *(w[1]) = *(w[2]);
1257
1258 if(w[3] != w[2] && !(*(w[3]+numpels)))
1259 *(w[3]) = *(w[2]);
1260 }
1261 }
1262 }
1263
ColorOutlinesRGBA(uint8_t * buffer,int width,int height)1264 void ColorOutlinesRGBA(uint8_t *buffer, int width, int height)
1265 {
1266 using namespace de;
1267
1268 uint32_t *buffer32 = reinterpret_cast<uint32_t *>(buffer);
1269
1270 for (int y = 0; y < height; ++y)
1271 {
1272 uint32_t *row = buffer32 + y * width;
1273 for (int x = 0; x < width; ++x)
1274 {
1275 if ((row[x] & 0xff000000) == 0) // transparent pixel
1276 {
1277 int average[3]{};
1278 int count = 0;
1279 for (int dy = -1; dy <= 1; ++dy)
1280 {
1281 for (int dx = -1; dx <= 1; ++dx)
1282 {
1283 if (!(dx || dy)) continue; // the current pixel
1284
1285 const Vec2i pos(x + dx, y + dy);
1286 if (pos.x >= 0 && pos.y >= 0 && pos.x < width && pos.y < height)
1287 {
1288 const uint32_t adjacent = buffer32[pos.y * width + pos.x];
1289 if (adjacent & 0xff000000) // non-transparent pixel
1290 {
1291 average[0] += adjacent & 0xff;
1292 average[1] += (adjacent >> 8) & 0xff;
1293 average[2] += (adjacent >> 16) & 0xff;
1294 ++count;
1295 }
1296 }
1297 }
1298 }
1299 if (count)
1300 {
1301 for (int &c : average) c /= count;
1302 }
1303 row[x] = average[0] | (average[1] << 8) | (average[2] << 16);
1304 }
1305 }
1306 }
1307 }
1308
EqualizeLuma(uint8_t * pixels,int width,int height,float * rBaMul,float * rHiMul,float * rLoMul)1309 void EqualizeLuma(uint8_t* pixels, int width, int height, float* rBaMul,
1310 float* rHiMul, float* rLoMul)
1311 {
1312 assert(pixels);
1313 {
1314 float hiMul, loMul, baMul;
1315 long wideAvg, numpels;
1316 uint8_t min, max, avg;
1317 uint8_t* pix;
1318
1319 if(width <= 0 || height <= 0)
1320 return;
1321
1322 numpels = width * height;
1323 min = 255;
1324 max = 0;
1325 wideAvg = 0;
1326
1327 { long i;
1328 for(i = 0, pix = pixels; i < numpels; ++i, pix += 1)
1329 {
1330 if(*pix < min) min = *pix;
1331 if(*pix > max) max = *pix;
1332 wideAvg += *pix;
1333 }}
1334
1335 if(max <= min || max == 0 || min == 255)
1336 {
1337 if(rBaMul) *rBaMul = -1;
1338 if(rHiMul) *rHiMul = -1;
1339 if(rLoMul) *rLoMul = -1;
1340 return; // Nothing we can do.
1341 }
1342
1343 avg = MIN_OF(255, wideAvg / numpels);
1344
1345 // Allow a small margin of variance with the balance multiplier.
1346 baMul = (!INRANGE_OF(avg, 127, 4)? (float)127/avg : 1);
1347 if(baMul != 1)
1348 {
1349 if(max < 255)
1350 max = (uint8_t) MINMAX_OF(1, (float)max - (255-max) * baMul, 255);
1351 if(min > 0)
1352 min = (uint8_t) MINMAX_OF(0, (float)min + min * baMul, 255);
1353 }
1354
1355 hiMul = (max < 255? (float)255/max : 1);
1356 loMul = (min > 0 ? 1-((float)min/255) : 1);
1357
1358 if(!(baMul == 1 && hiMul == 1 && loMul == 1))
1359 {
1360 long i;
1361 for(i = 0, pix = pixels; i < numpels; ++i, pix += 1)
1362 {
1363 // First balance.
1364 float val = baMul * (*pix);
1365 // Now amplify.
1366 if(val > 127) val *= hiMul;
1367 else val *= loMul;
1368
1369 *pix = (uint8_t) MINMAX_OF(0, val, 255);
1370 }
1371 }
1372
1373 if(rBaMul) *rBaMul = baMul;
1374 if(rHiMul) *rHiMul = hiMul;
1375 if(rLoMul) *rLoMul = loMul;
1376 }
1377 }
1378
Desaturate(uint8_t * pixels,int width,int height,int comps)1379 void Desaturate(uint8_t* pixels, int width, int height, int comps)
1380 {
1381 assert(pixels);
1382 {
1383 uint8_t* pix;
1384 long i, numpels;
1385
1386 if(width <= 0 || height <= 0)
1387 return;
1388
1389 numpels = width * height;
1390 for(i = 0, pix = pixels; i < numpels; ++i, pix += comps)
1391 {
1392 int min = MIN_OF(pix[0], MIN_OF(pix[1], pix[2]));
1393 int max = MAX_OF(pix[0], MAX_OF(pix[1], pix[2]));
1394 pix[0] = pix[1] = pix[2] = (min + max) / 2;
1395 }
1396 }
1397 }
1398
AmplifyLuma(uint8_t * pixels,int width,int height,dd_bool hasAlpha)1399 void AmplifyLuma(uint8_t* pixels, int width, int height, dd_bool hasAlpha)
1400 {
1401 assert(pixels);
1402 {
1403 long numPels;
1404 uint8_t max = 0;
1405
1406 if(width <= 0 || height <= 0)
1407 return;
1408
1409 numPels = width * height;
1410 if(hasAlpha)
1411 {
1412 uint8_t* pix = pixels;
1413 uint8_t* apix = pixels + numPels;
1414 long i;
1415 for(i = 0; i < numPels; ++i, pix++, apix++)
1416 {
1417 // Only non-masked pixels count.
1418 if(!(*apix > 0))
1419 continue;
1420
1421 if(*pix > max)
1422 max = *pix;
1423 }
1424 }
1425 else
1426 {
1427 uint8_t* pix = pixels;
1428 long i;
1429 for(i = 0; i < numPels; ++i, pix++)
1430 {
1431 if(*pix > max)
1432 max = *pix;
1433 }
1434 }
1435
1436 if(0 == max || 255 == max)
1437 return;
1438
1439 { uint8_t* pix = pixels;
1440 long i;
1441 for(i = 0; i < numPels; ++i, pix++)
1442 {
1443 *pix = (uint8_t) MINMAX_OF(0, (float)*pix / max * 255, 255);
1444 }}
1445 }
1446 }
1447
EnhanceContrast(uint8_t * pixels,int width,int height,int comps)1448 void EnhanceContrast(uint8_t* pixels, int width, int height, int comps)
1449 {
1450 assert(pixels);
1451 {
1452 uint8_t* pix;
1453 long i, numpels;
1454
1455 if(width <= 0 || height <= 0)
1456 return;
1457
1458 if(comps != 3 && comps != 4)
1459 {
1460 App_Log(DE2_DEV_GL_ERROR, "EnhanceContrast: comps=%i", comps);
1461 DENG_ASSERT(!"EnhanceContrast: Attempted on non-rgb(a) image.");
1462 return;
1463 }
1464
1465 pix = pixels;
1466 numpels = width * height;
1467
1468 for(i = 0; i < numpels; ++i, pix += comps)
1469 {
1470 int c;
1471 for(c = 0; c < 3; ++c)
1472 {
1473 uint8_t out;
1474 if(pix[c] < 60) // Darken dark parts.
1475 out = (uint8_t) MINMAX_OF(0, ((float)pix[c] - 70) * 1.0125f + 70, 255);
1476 else if(pix[c] > 185) // Lighten light parts.
1477 out = (uint8_t) MINMAX_OF(0, ((float)pix[c] - 185) * 1.0125f + 185, 255);
1478 else
1479 out = pix[c];
1480 pix[c] = out;
1481 }
1482 }
1483 }
1484 }
1485
SharpenPixels(uint8_t * pixels,int width,int height,int comps)1486 void SharpenPixels(uint8_t* pixels, int width, int height, int comps)
1487 {
1488 assert(pixels);
1489 {
1490 const float strength = .05f;
1491 uint8_t* result;
1492 float A, B, C;
1493 int x, y;
1494
1495 if(width <= 0 || height <= 0)
1496 return;
1497
1498 if(comps != 3 && comps != 4)
1499 {
1500 App_Log(DE2_DEV_GL_ERROR, "SharpenPixels: comps=%i", comps);
1501 DENG_ASSERT(!"SharpenPixels: Attempted on non-rgb(a) image.");
1502 return;
1503 }
1504
1505 result = (uint8_t *) M_Calloc(comps * width * height);
1506
1507 A = strength;
1508 B = .70710678f * strength; // 1/sqrt(2)
1509 C = 1 + 4*A + 4*B;
1510
1511 for(y = 1; y < height - 1; ++y)
1512 for(x = 1; x < width -1; ++x)
1513 {
1514 const uint8_t* pix = pixels + (x + y*width) * comps;
1515 uint8_t* out = result + (x + y*width) * comps;
1516 int c;
1517 for(c = 0; c < 3; ++c)
1518 {
1519 int r = (C*pix[c] - A*pix[c - width] - A*pix[c + comps] - A*pix[c - comps] -
1520 A*pix[c + width] - B*pix[c + comps - width] - B*pix[c + comps + width] -
1521 B*pix[c - comps - width] - B*pix[c - comps + width]);
1522 out[c] = MINMAX_OF(0, r, 255);
1523 }
1524
1525 if(comps == 4)
1526 out[3] = pix[3];
1527 }
1528
1529 memcpy(pixels, result, comps * width * height);
1530 free(result);
1531 }
1532 }
1533
1534 /**
1535 * @return @c true, if the given color is either (0,255,255) or (255,0,255).
1536 */
isKeyedColor(uint8_t * color)1537 static inline bool isKeyedColor(uint8_t *color)
1538 {
1539 DENG2_ASSERT(color);
1540 return color[2] == 0xff && ((color[0] == 0xff && color[1] == 0) ||
1541 (color[0] == 0 && color[1] == 0xff));
1542 }
1543
1544 /**
1545 * Buffer must be RGBA. Doesn't touch the non-keyed pixels.
1546 */
doColorKeying(uint8_t * rgbaBuf,int width)1547 static void doColorKeying(uint8_t *rgbaBuf, int width)
1548 {
1549 DENG2_ASSERT(rgbaBuf);
1550
1551 for(int i = 0; i < width; ++i, rgbaBuf += 4)
1552 {
1553 if(!isKeyedColor(rgbaBuf)) continue;
1554
1555 rgbaBuf[3] = rgbaBuf[2] = rgbaBuf[1] = rgbaBuf[0] = 0;
1556 }
1557 }
1558
ApplyColorKeying(uint8_t * buf,int width,int height,int pixelSize)1559 uint8_t *ApplyColorKeying(uint8_t *buf, int width, int height, int pixelSize)
1560 {
1561 DENG2_ASSERT(buf);
1562
1563 if(width <= 0 || height <= 0)
1564 return buf;
1565
1566 // We must allocate a new buffer if the loaded image has less than the
1567 // required number of color components.
1568 if(pixelSize < 4)
1569 {
1570 long const numpels = width * height;
1571 uint8_t *ckdest = (uint8_t *) M_Malloc(4 * numpels);
1572 uint8_t *in, *out;
1573 long i;
1574
1575 for(in = buf, out = ckdest, i = 0; i < numpels; ++i, in += pixelSize, out += 4)
1576 {
1577 if(isKeyedColor(in))
1578 {
1579 std::memset(out, 0, 4); // Totally black.
1580 continue;
1581 }
1582
1583 std::memcpy(out, in, 3); // The color itself.
1584 out[3] = 255; // Opaque.
1585 }
1586 return ckdest;
1587 }
1588
1589 // We can do the keying in-buffer.
1590 // This preserves the alpha values of non-keyed pixels.
1591 for(int i = 0; i < height; ++i)
1592 {
1593 doColorKeying(buf + 4 * i * width, height);
1594 }
1595 return buf;
1596 }
1597