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