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