1 // functions taken from qimageblitz (no longer maintained)
2 
3 /*
4  Copyright (C) 1998, 1999, 2001, 2002, 2004, 2005, 2007
5       Daniel M. Duley <daniel.duley@verizon.net>
6  (C) 2004 Zack Rusin <zack@kde.org>
7  (C) 2000 Josef Weidendorfer <weidendo@in.tum.de>
8  (C) 1999 Geert Jansen <g.t.jansen@stud.tue.nl>
9  (C) 1998, 1999 Christian Tibirna <ctibirna@total.net>
10  (C) 1998, 1999 Dirk Mueller <mueller@kde.org>
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions
14 are met:
15 
16 1. Redistributions of source code must retain the above copyright
17    notice, this list of conditions and the following disclaimer.
18 2. Redistributions in binary form must reproduce the above copyright
19    notice, this list of conditions and the following disclaimer in the
20    documentation and/or other materials provided with the distribution.
21 
22 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 
33 */
34 
35 /*
36  Portions of this software were originally based on ImageMagick's
37  algorithms. ImageMagick is copyrighted under the following conditions:
38 
39 Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated to
40 making software imaging solutions freely available.
41 
42 Permission is hereby granted, free of charge, to any person obtaining a copy
43 of this software and associated documentation files ("ImageMagick"), to deal
44 in ImageMagick without restriction, including without limitation the rights
45 to use, copy, modify, merge, publish, distribute, sublicense,  and/or sell
46 copies of ImageMagick, and to permit persons to whom the ImageMagick is
47 furnished to do so, subject to the following conditions:
48 
49 The above copyright notice and this permission notice shall be included in all
50 copies or substantial portions of ImageMagick.
51 
52 The software is provided "as is", without warranty of any kind, express or
53 implied, including but not limited to the warranties of merchantability,
54 fitness for a particular purpose and noninfringement.  In no event shall
55 ImageMagick Studio be liable for any claim, damages or other liability,
56 whether in an action of contract, tort or otherwise, arising from, out of or
57 in connection with ImageMagick or the use or other dealings in ImageMagick.
58 
59 Except as contained in this notice, the name of the ImageMagick Studio shall
60 not be used in advertising or otherwise to promote the sale, use or other
61 dealings in ImageMagick without prior written authorization from the
62 ImageMagick Studio.
63 */
64 
65 #include "blitz.h"
66 
67 #include <QColor>
68 #include <cmath>
69 
70 #define M_SQ2PI 2.50662827463100024161235523934010416269302368164062
71 #define M_EPSILON 1.0e-6
72 
73 #define CONVOLVE_ACC(weight, pixel) \
74     r+=((weight))*(qRed((pixel))); g+=((weight))*(qGreen((pixel))); \
75     b+=((weight))*(qBlue((pixel)));
76 
77 //--------------------------------------------------------------------------------
78 
convertFromPremult(QRgb p)79 inline QRgb convertFromPremult(QRgb p)
80 {
81     int alpha = qAlpha(p);
82     return(!alpha ? 0 : qRgba(255*qRed(p)/alpha,
83                               255*qGreen(p)/alpha,
84                               255*qBlue(p)/alpha,
85                               alpha));
86 }
87 
88 //--------------------------------------------------------------------------------
89 
convertToPremult(QRgb p)90 inline QRgb convertToPremult(QRgb p)
91 {
92     unsigned int a = p >> 24;
93     unsigned int t = (p & 0xff00ff) * a;
94     t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8;
95     t &= 0xff00ff;
96 
97     p = ((p >> 8) & 0xff) * a;
98     p = (p + ((p >> 8) & 0xff) + 0x80);
99     p &= 0xff00;
100     p |= t | (a << 24);
101     return(p);
102 }
103 
104 //--------------------------------------------------------------------------------
105 // These are used as accumulators
106 
107 typedef struct
108 {
109     quint32 red, green, blue, alpha;
110 } IntegerPixel;
111 
112 typedef struct
113 {
114     // Yes, a normal pixel can be used instead but this is easier to read
115     // and no shifts to get components.
116     quint8 red, green, blue, alpha;
117 } CharPixel;
118 
119 typedef struct
120 {
121     quint32 red, green, blue, alpha;
122 } HistogramListItem;
123 
124 
equalize(QImage & img)125 bool equalize(QImage &img)
126 {
127     if(img.isNull()) {
128         return(false);
129     }
130 
131     HistogramListItem *histogram;
132     IntegerPixel *map;
133     IntegerPixel intensity, high, low;
134     CharPixel *equalize_map;
135     int i, count;
136     QRgb pixel, *dest;
137     unsigned char r, g, b;
138 
139     if(img.depth() < 32){
140         img = img.convertToFormat(img.hasAlphaChannel() ?
141                                   QImage::Format_ARGB32 :
142                                   QImage::Format_RGB32);
143     }
144     count = img.width()*img.height();
145 
146     map = new IntegerPixel[256];
147     histogram = new HistogramListItem[256];
148     equalize_map = new CharPixel[256];
149 
150     // form histogram
151     memset(histogram, 0, 256*sizeof(HistogramListItem));
152     dest = reinterpret_cast<QRgb *>(img.bits());
153 
154     if(img.format() == QImage::Format_ARGB32_Premultiplied){
155         for(i=0; i < count; ++i, ++dest){
156             pixel = convertFromPremult(*dest);
157             histogram[qRed(pixel)].red++;
158             histogram[qGreen(pixel)].green++;
159             histogram[qBlue(pixel)].blue++;
160             histogram[qAlpha(pixel)].alpha++;
161         }
162     }
163     else{
164         for(i=0; i < count; ++i){
165             pixel = *dest++;
166             histogram[qRed(pixel)].red++;
167             histogram[qGreen(pixel)].green++;
168             histogram[qBlue(pixel)].blue++;
169             histogram[qAlpha(pixel)].alpha++;
170         }
171     }
172 
173     // integrate the histogram to get the equalization map
174     memset(&intensity, 0, sizeof(IntegerPixel));
175     for(i=0; i < 256; ++i){
176         intensity.red += histogram[i].red;
177         intensity.green += histogram[i].green;
178         intensity.blue += histogram[i].blue;
179         map[i] = intensity;
180     }
181 
182     low = map[0];
183     high = map[255];
184     memset(equalize_map, 0, 256*sizeof(CharPixel));
185     for(i=0; i < 256; ++i){
186         if(high.red != low.red) {
187             equalize_map[i].red = static_cast<unsigned char>
188                     ((255*(map[i].red-low.red))/(high.red-low.red));
189         }
190         if(high.green != low.green) {
191             equalize_map[i].green = static_cast<unsigned char>
192                     ((255*(map[i].green-low.green))/(high.green-low.green));
193         }
194         if(high.blue != low.blue) {
195             equalize_map[i].blue = static_cast<unsigned char>
196                     ((255*(map[i].blue-low.blue))/(high.blue-low.blue));
197         }
198     }
199 
200     // stretch the histogram and write
201     dest = reinterpret_cast<QRgb *>(img.bits());
202     if(img.format() == QImage::Format_ARGB32_Premultiplied){
203         for(i=0; i < count; ++i, ++dest){
204             pixel = convertFromPremult(*dest);
205             r = static_cast<unsigned char> ((low.red != high.red) ?
206                                                 equalize_map[qRed(pixel)].red : qRed(pixel));
207 
208             g = static_cast<unsigned char> ((low.green != high.green) ?
209                                                 equalize_map[qGreen(pixel)].green : qGreen(pixel));
210 
211             b = static_cast<unsigned char> ((low.blue != high.blue) ?
212                                                 equalize_map[qBlue(pixel)].blue : qBlue(pixel));
213 
214             *dest = convertToPremult(qRgba(r, g, b, qAlpha(pixel)));
215         }
216     }
217     else{
218         for(i=0; i < count; ++i){
219             pixel = *dest;
220             r = static_cast<unsigned char> ((low.red != high.red) ?
221                                                 equalize_map[qRed(pixel)].red : qRed(pixel));
222 
223             g = static_cast<unsigned char> ((low.green != high.green) ?
224                                                 equalize_map[qGreen(pixel)].green : qGreen(pixel));
225 
226             b = static_cast<unsigned char> ((low.blue != high.blue) ?
227                                                 equalize_map[qBlue(pixel)].blue : qBlue(pixel));
228 
229             *dest++ = qRgba(r, g, b, qAlpha(pixel));
230         }
231     }
232 
233     delete[] histogram;
234     delete[] map;
235     delete[] equalize_map;
236     return(true);
237 }
238 
239 //--------------------------------------------------------------------------------
240 
blur(QImage & img,int radius)241 QImage Blitz::blur(QImage &img, int radius)
242 {
243     if (img.isNull()) {
244         return (img);
245     }
246 
247     if (img.depth() < 8) {
248         img = img.convertToFormat(QImage::Format_Indexed8);
249     }
250 
251     QVector<QRgb> colorTable;
252     if (img.format() == QImage::Format_Indexed8) {
253         colorTable = img.colorTable();
254     }
255 
256     auto width = img.width();
257     auto height = img.height();
258 
259     QImage buffer(width, height, img.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);
260 
261     const auto img_format = img.format();
262 
263     int *as = new int[width];
264     int *rs = new int[width];
265     int *gs = new int[width];
266     int *bs = new int[width];
267 
268     QRgb *p1 , *p2;
269 
270     for (auto y = 0; y < height; ++y) {
271         auto my = y - radius;
272         auto mh = (radius << 1) + 1;
273 
274         if (my < 0) {
275             mh += my;
276             my = 0;
277         }
278 
279         if ((my + mh) > height) {
280             mh = height - my;
281         }
282 
283         p1 = reinterpret_cast<QRgb *>(buffer.scanLine(y));
284 
285         memset(as, 0, static_cast<unsigned int>(width) * sizeof(int));
286         memset(rs, 0, static_cast<unsigned int>(width) * sizeof(int));
287         memset(gs, 0, static_cast<unsigned int>(width) * sizeof(int));
288         memset(bs, 0, static_cast<unsigned int>(width) * sizeof(int));
289 
290 
291         switch (img_format) {
292         case QImage::Format_ARGB32_Premultiplied: {
293             QRgb pixel;
294             for (auto i = 0; i < mh; i++) {
295                 p2 = reinterpret_cast<QRgb *>(img.scanLine(i + my));
296                 for (auto j = 0; j < width; ++j) {
297                     p2++;
298                     pixel = convertFromPremult(*p2);
299                     as[j] += qAlpha(pixel);
300                     rs[j] += qRed(pixel) * qRed(pixel);
301                     gs[j] += qGreen(pixel) * qGreen(pixel);
302                     bs[j] += qBlue(pixel) * qBlue(pixel);
303                 }
304             }
305             break;
306         }
307 
308         case QImage::Format_Indexed8: {
309             QRgb pixel;
310             unsigned char *ptr;
311             for (auto i = 0; i < mh; ++i) {
312                 ptr = img.scanLine(i + my);
313                 for (auto j = 0; j < width; ++j) {
314                     ptr++;
315                     pixel = colorTable[*ptr];
316                     as[j] += qAlpha(pixel);
317                     rs[j] += qRed(pixel) * qRed(pixel);
318                     gs[j] += qGreen(pixel) * qGreen(pixel);
319                     bs[j] += qBlue(pixel) * qBlue(pixel);
320                 }
321             }
322             break;
323         }
324 
325         default: {
326             for (auto i = 0; i < mh; ++i) {
327                 p2 = reinterpret_cast<QRgb *>(img.scanLine(i + my));
328                 for (auto j = 0; j < width; j++) {
329                     p2++;
330                     as[j] += qAlpha(*p2);
331                     rs[j] += qRed(*p2);
332                     gs[j] += qGreen(*p2);
333                     bs[j] += qBlue(*p2);
334                 }
335             }
336             break;
337         }
338         }
339 
340         for (auto i = 0; i < width; ++i) {
341             auto a{0};
342             auto r{0};
343             auto g{0};
344             auto b{0};
345 
346             auto mx = i - radius;
347             auto mw = (radius << 1) + 1;
348 
349             if (mx < 0) {
350                 mw += mx;
351                 mx = 0;
352             }
353 
354             if ((mx + mw) > width) {
355                 mw = width - mx;
356             }
357 
358             for (auto j = mx; j < (mw + mx); ++j) {
359                 a += as[j];
360                 r += rs[j];
361                 g += gs[j];
362                 b += bs[j];
363             }
364 
365             auto mt = mw * mh;
366 
367             a = a / mt;
368             r = r / mt;
369             g = g / mt;
370             b = b / mt;
371 
372             *p1++ = qRgba(std::sqrt(r), std::sqrt(g), std::sqrt(b), a);
373         }
374     }
375 
376     delete[] as;
377     delete[] rs;
378     delete[] gs;
379     delete[] bs;
380 
381     return (buffer);
382 }
383 
384 //--------------------------------------------------------------------------------
385 
defaultConvolveMatrixSize(float radius,float sigma,bool quality)386 int defaultConvolveMatrixSize(float radius, float sigma, bool quality)
387 {
388     int i, matrix_size;
389     float normalize, value;
390     float sigma2 = sigma*sigma*2.0f;
391     float sigmaSQ2PI = static_cast<float>(M_SQ2PI) * sigma;
392     int max = quality ? 65535 : 255;
393 
394     if(sigma == 0.0f){
395         qWarning("Blitz::defaultConvolveMatrixSize(): Zero sigma is invalid!");
396         return(5);
397     }
398 
399     if(radius > 0.0f) {
400         return(static_cast<int>(2.0f * std::ceil(radius) + 1.0f));
401     }
402 
403     matrix_size = 5;
404     do{
405         normalize = 0.0;
406         for(i=(-matrix_size/2); i <= (matrix_size/2); ++i) {
407             normalize += std::exp(-(static_cast<float> (i*i))/sigma2) / sigmaSQ2PI;
408         }
409         i = matrix_size/2;
410         value = std::exp(-(static_cast<float> (i*i))/sigma2) / sigmaSQ2PI / normalize;
411         matrix_size += 2;
412     } while(static_cast<int>(max*value) > 0);
413 
414     matrix_size-=4;
415     return(matrix_size);
416 }
417 
418 //--------------------------------------------------------------------------------
419 
convolve(QImage & img,int matrix_size,float * matrix)420 QImage convolve(QImage &img, int matrix_size, float *matrix)
421 {
422     int i, x, y, w, h, matrix_x, matrix_y;
423     int edge = matrix_size/2;
424     QRgb *dest, *src, *s, **scanblock;
425     float *m, *normalize_matrix, normalize;
426 
427     if(!(matrix_size % 2)){
428         qWarning("Blitz::convolve(): kernel width must be an odd number!");
429         return(img);
430     }
431 
432     w = img.width();
433     h = img.height();
434     if(w < 3 || h < 3){
435         qWarning("Blitz::convolve(): Image is too small!");
436         return(img);
437     }
438 
439     if(img.format() == QImage::Format_ARGB32_Premultiplied) {
440         img = img.convertToFormat(QImage::Format_ARGB32);
441     }
442     else if(img.depth() < 32){
443         img = img.convertToFormat(img.hasAlphaChannel() ?
444                                   QImage::Format_ARGB32 :
445                                   QImage::Format_RGB32);
446     }
447     QImage buffer(w, h, img.format());
448 
449     scanblock = new QRgb* [matrix_size];
450     normalize_matrix = new float[matrix_size*matrix_size];
451 
452     // create normalized matrix
453     normalize = 0.0;
454     for(i=0; i < matrix_size*matrix_size; ++i) {
455         normalize += matrix[i];
456     }
457     if(std::abs(normalize) <=  static_cast<float> (M_EPSILON)) {
458         normalize = 1.0f;
459     }
460     normalize = 1.0f/normalize;
461     for(i=0; i < matrix_size*matrix_size; ++i){
462         normalize_matrix[i] = normalize*matrix[i];
463     }
464 
465     // apply
466 
467     {
468         //
469         //
470         // Non-MMX version
471         //
472         //
473 
474         float r, g, b;
475         for(y=0; y < h; ++y){
476             src = reinterpret_cast<QRgb *>(img.scanLine(y));
477             dest = reinterpret_cast<QRgb *>(buffer.scanLine(y));
478             // Read in scanlines to pixel neighborhood. If the scanline is outside
479             // the image use the top or bottom edge.
480             for(x=y-edge, i=0; x <= y+edge; ++i, ++x){
481                 scanblock[i] = reinterpret_cast<QRgb *>(
482                     img.scanLine((x < 0) ? 0 : (x > h-1) ? h-1 : x));
483             }
484             // Now we are about to start processing scanlines. First handle the
485             // part where the pixel neighborhood extends off the left edge.
486             for(x=0; x-edge < 0 ; ++x){
487                 r = g = b = 0.0;
488                 m = normalize_matrix;
489                 for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){
490                     s = scanblock[matrix_y];
491                     matrix_x = -edge;
492                     while(x+matrix_x < 0){
493                         CONVOLVE_ACC(*m, *s);
494                         ++matrix_x; ++m;
495                     }
496                     while(matrix_x <= edge){
497                         CONVOLVE_ACC(*m, *s);
498                         ++matrix_x; ++m; ++s;
499                     }
500                 }
501                 r = r < 0.0f ? 0.0f : r > 255.0f ? 255.0f : r + 0.5f;
502                 g = g < 0.0f ? 0.0f : g > 255.0f ? 255.0f : g + 0.5f;
503                 b = b < 0.0f ? 0.0f : b > 255.0f ? 255.0f : b + 0.5f;
504                 *dest++ = qRgba(static_cast<unsigned char> (r), static_cast<unsigned char> (g),
505                                 static_cast<unsigned char> (b), qAlpha(*src++));
506             }
507             // Okay, now process the middle part where the entire neighborhood
508             // is on the image.
509             for(; x+edge < w; ++x){
510                 m = normalize_matrix;
511                 r = g = b = 0.0;
512                 for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){
513                     s = scanblock[matrix_y] + (x-edge);
514                     for(matrix_x = -edge; matrix_x <= edge; ++matrix_x, ++m, ++s){
515                         CONVOLVE_ACC(*m, *s);
516                     }
517                 }
518                 r = r < 0.0f ? 0.0f : r > 255.0f ? 255.0f : r + 0.5f;
519                 g = g < 0.0f ? 0.0f : g > 255.0f ? 255.0f : g + 0.5f;
520                 b = b < 0.0f ? 0.0f : b > 255.0f ? 255.0f : b + 0.5f;
521                 *dest++ = qRgba(static_cast<unsigned char> (r), static_cast<unsigned char> (g),
522                                 static_cast<unsigned char> (b), qAlpha(*src++));
523             }
524             // Finally process the right part where the neighborhood extends off
525             // the right edge of the image
526             for(; x < w; ++x){
527                 r = g = b = 0.0;
528                 m = normalize_matrix;
529                 for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){
530                     s = scanblock[matrix_y];
531                     s += x-edge;
532                     matrix_x = -edge;
533                     while(x+matrix_x < w){
534                         CONVOLVE_ACC(*m, *s);
535                         ++matrix_x;
536                         ++m;
537                         ++s;
538                     }
539                     --s;
540                     while(matrix_x <= edge){
541                         CONVOLVE_ACC(*m, *s);
542                         ++matrix_x;
543                         ++m;
544                     }
545                 }
546                 r = r < 0.0f ? 0.0f : r > 255.0f ? 255.0f : r + 0.5f;
547                 g = g < 0.0f ? 0.0f : g > 255.0f ? 255.0f : g + 0.5f;
548                 b = b < 0.0f ? 0.0f : b > 255.0f ? 255.0f : b + 0.5f;
549                 *dest++ = qRgba(static_cast<unsigned char> (r), static_cast<unsigned char> (g),
550                                 static_cast<unsigned char> (b), qAlpha(*src++));
551             }
552         }
553     }
554 
555     delete[] scanblock;
556     delete[] normalize_matrix;
557     return(buffer);
558 }
559 
560 //--------------------------------------------------------------------------------
561 
gaussianSharpen(QImage & img,float radius,float sigma)562 QImage Blitz::gaussianSharpen(QImage &img, float radius, float sigma)
563 {
564     if(sigma == 0.0f){
565         qWarning("Blitz::gaussianSharpen(): Zero sigma is invalid!");
566         return(img);
567     }
568 
569     int matrix_size = defaultConvolveMatrixSize(radius, sigma, true);
570     int len = matrix_size*matrix_size;
571     float alpha, *matrix = new float[len];
572     float sigma2 = sigma*sigma*2.0f;
573     float sigmaPI2 = 2.0f*static_cast<float> (M_PI)*sigma*sigma;
574 
575     int half = matrix_size/2;
576     int x, y, i=0, j=half;
577     float normalize=0.0;
578     for(y=(-half); y <= half; ++y, --j){
579         for(x=(-half); x <= half; ++x, ++i){
580             alpha = std::exp(-(static_cast<float> (x*x+y*y))/sigma2);
581             matrix[i] = alpha/sigmaPI2;
582             normalize += matrix[i];
583         }
584     }
585 
586     matrix[i/2]=(-2.0f)*normalize;
587     QImage result(convolve(img, matrix_size, matrix));
588     delete[] matrix;
589     return(result);
590 }
591 
592 //--------------------------------------------------------------------------------
593 
emboss(QImage & img,float radius,float sigma)594 QImage Blitz::emboss(QImage &img, float radius, float sigma)
595 {
596     if(sigma == 0.0f){
597         qWarning("Blitz::emboss(): Zero sigma is invalid!");
598         return(img);
599     }
600 
601     int matrix_size = defaultConvolveMatrixSize(radius, sigma, true);
602     int len = matrix_size*matrix_size;
603 
604     float alpha, *matrix = new float[len];
605     float sigma2 = sigma*sigma*2.0f;
606     float sigmaPI2 = 2.0f*static_cast<float> (M_PI)*sigma*sigma;
607 
608     int half = matrix_size/2;
609     int x, y, i=0, j=half;
610     for(y=(-half); y <= half; ++y, --j){
611         for(x=(-half); x <= half; ++x, ++i){
612             alpha = std::exp(-(static_cast<float> (x*x+y*y))/sigma2);
613             matrix[i]=((x < 0) || (y < 0) ? -8.0f : 8.0f)*alpha/sigmaPI2;
614             if(x == j) {
615                 matrix[i]=0.0;
616             }
617         }
618     }
619     QImage result(convolve(img, matrix_size, matrix));
620     delete[] matrix;
621     equalize(result);
622     return(result);
623 }
624 
625 //--------------------------------------------------------------------------------
626 
flatten(QImage & img,const QColor & ca,const QColor & cb)627 QImage& Blitz::flatten(QImage &img, const QColor &ca, const QColor &cb)
628 {
629     if(img.isNull()) {
630         return(img);
631     }
632 
633     if(img.depth() == 1) {
634         img.setColor(0, ca.rgb());
635         img.setColor(1, cb.rgb());
636         return(img);
637     }
638 
639     int r1 = ca.red(); int r2 = cb.red();
640     int g1 = ca.green(); int g2 = cb.green();
641     int b1 = ca.blue(); int b2 = cb.blue();
642     int min = 0, max = 255;
643 
644     QRgb *data, *end;
645     QVector<QRgb> cTable;
646     if(img.format() == QImage::Format_Indexed8){
647         cTable = img.colorTable();
648         data = static_cast<unsigned int *> (cTable.data());
649         end = data + img.colorCount();
650 
651     }
652     else{
653         data = reinterpret_cast<QRgb *>(img.scanLine(0));
654         end = data + (img.width()*img.height());
655     }
656 
657     // get minimum and maximum graylevel
658     QRgb *ptr = data;
659     int mean;
660 
661     if(img.format() != QImage::Format_ARGB32_Premultiplied){
662         while(ptr != end){
663             mean = (qRed(*ptr) + qGreen(*ptr) + qBlue(*ptr)) / 3;
664             min = qMin(min, mean);
665             max = qMax(max, mean);
666             ++ptr;
667         }
668     }
669     else{
670         QRgb pixel;
671         while(ptr != end){
672             pixel = convertFromPremult(*ptr);
673             mean = (qRed(pixel) + qGreen(pixel) + qBlue(pixel)) / 3;
674             min = qMin(min, mean);
675             max = qMax(max, mean);
676             ++ptr;
677         }
678     }
679 
680     // conversion factors
681     float sr = (static_cast<float> (r2 - r1) / (max - min));
682     float sg = (static_cast<float> (g2 - g1) / (max - min));
683     float sb = (static_cast<float> (b2 - b1) / (max - min));
684 
685     if(img.format() != QImage::Format_ARGB32_Premultiplied){
686         while(data != end){
687             mean = (qRed(*data) + qGreen(*data) + qBlue(*data)) / 3;
688             *data = qRgba(static_cast<unsigned char> (sr * (mean - min) + r1 + 0.5f),
689                           static_cast<unsigned char> (sg * (mean - min) + g1 + 0.5f),
690                           static_cast<unsigned char> (sb * (mean - min) + b1 + 0.5f),
691                           qAlpha(*data));
692             ++data;
693         }
694     }
695     else{
696         QRgb pixel;
697         while(data != end){
698             pixel = convertFromPremult(*data);
699             mean = (qRed(pixel) + qGreen(pixel) + qBlue(pixel)) / 3;
700             *data =
701                 convertToPremult(qRgba(static_cast<unsigned char> (sr * (mean - min) + r1 + 0.5f),
702                                        static_cast<unsigned char> (sg * (mean - min) + g1 + 0.5f),
703                                        static_cast<unsigned char> (sb * (mean - min) + b1 + 0.5f),
704                                        qAlpha(*data)));
705             ++data;
706         }
707     }
708 
709     if(img.format() == QImage::Format_Indexed8) {
710         img.setColorTable(cTable);
711     }
712     return(img);
713 }
714 
715 //--------------------------------------------------------------------------------
716