1 // xImaDsp.cpp : DSP functions
\r
2 /* 07/08/2001 v1.00 - Davide Pizzolato - www.xdp.it
\r
3 * CxImage version 6.0.0 02/Feb/2008
\r
8 #include "ximaiter.h"
\r
10 #if CXIMAGE_SUPPORT_DSP
\r
12 ////////////////////////////////////////////////////////////////////////////////
\r
14 * Converts the image to B&W.
\r
15 * The OptimalThreshold() function can be used for calculating the optimal threshold.
\r
16 * \param level: the lightness threshold.
\r
17 * \return true if everything is ok
\r
19 bool CxImage::Threshold(BYTE level)
\r
21 if (!pDib) return false;
\r
22 if (head.biBitCount == 1) return true;
\r
26 CxImage tmp(head.biWidth,head.biHeight,1);
\r
27 if (!tmp.IsValid()){
\r
28 strcpy(info.szLastError,tmp.GetLastError());
\r
32 for (long y=0;y<head.biHeight;y++){
\r
33 info.nProgress = (long)(100*y/head.biHeight);
\r
34 if (info.nEscape) break;
\r
35 for (long x=0;x<head.biWidth;x++){
\r
36 if (BlindGetPixelIndex(x,y)>level)
\r
37 tmp.BlindSetPixelIndex(x,y,1);
\r
39 tmp.BlindSetPixelIndex(x,y,0);
\r
42 tmp.SetPaletteColor(0,0,0,0);
\r
43 tmp.SetPaletteColor(1,255,255,255);
\r
47 ////////////////////////////////////////////////////////////////////////////////
\r
49 * Converts the image to B&W, using a threshold mask
\r
50 * \param pThresholdMask: the lightness threshold mask.
\r
51 * the pThresholdMask image must be grayscale with same with and height of the current image
\r
52 * \return true if everything is ok
\r
54 bool CxImage::Threshold(CxImage* pThresholdMask)
\r
56 if (!pDib) return false;
\r
57 if (head.biBitCount == 1) return true;
\r
59 if (!pThresholdMask) return false;
\r
61 if (!pThresholdMask->IsValid() ||
\r
62 !pThresholdMask->IsGrayScale() ||
\r
63 pThresholdMask->GetWidth() != GetWidth() ||
\r
64 pThresholdMask->GetHeight() != GetHeight()){
\r
65 strcpy(info.szLastError,"invalid ThresholdMask");
\r
71 CxImage tmp(head.biWidth,head.biHeight,1);
\r
72 if (!tmp.IsValid()){
\r
73 strcpy(info.szLastError,tmp.GetLastError());
\r
77 for (long y=0;y<head.biHeight;y++){
\r
78 info.nProgress = (long)(100*y/head.biHeight);
\r
79 if (info.nEscape) break;
\r
80 for (long x=0;x<head.biWidth;x++){
\r
81 if (BlindGetPixelIndex(x,y)>pThresholdMask->BlindGetPixelIndex(x,y))
\r
82 tmp.BlindSetPixelIndex(x,y,1);
\r
84 tmp.BlindSetPixelIndex(x,y,0);
\r
87 tmp.SetPaletteColor(0,0,0,0);
\r
88 tmp.SetPaletteColor(1,255,255,255);
\r
92 ////////////////////////////////////////////////////////////////////////////////
\r
94 * Filters only the pixels with a lightness less (or more) than the threshold level,
\r
95 * and preserves the colors for the unfiltered pixels.
\r
96 * \param level = the lightness threshold.
\r
97 * \param bDirection = false: filter dark pixels, true: filter light pixels
\r
98 * \param nBkgndColor = filtered pixels are set to nBkgndColor color
\r
99 * \param bSetAlpha = if true, sets also the alpha component for the filtered pixels, with nBkgndColor.rgbReserved
\r
100 * \return true if everything is ok
\r
101 * \author [DP], [wangsongtao]
\r
103 ////////////////////////////////////////////////////////////////////////////////
\r
104 bool CxImage::Threshold2(BYTE level, bool bDirection, RGBQUAD nBkgndColor, bool bSetAlpha)
\r
106 if (!pDib) return false;
\r
107 if (head.biBitCount == 1) return true;
\r
109 CxImage tmp(*this, true, false, false);
\r
110 if (!tmp.IsValid()){
\r
111 strcpy(info.szLastError,tmp.GetLastError());
\r
117 long xmin,xmax,ymin,ymax;
\r
119 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
120 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
123 xmax = head.biWidth; ymax=head.biHeight;
\r
126 for(long y=ymin; y<ymax; y++){
\r
127 info.nProgress = (long)(100*y/head.biHeight);
\r
128 if (info.nEscape) break;
\r
129 for(long x=xmin; x<xmax; x++){
\r
130 #if CXIMAGE_SUPPORT_SELECTION
\r
131 if (BlindSelectionIsInside(x,y))
\r
132 #endif //CXIMAGE_SUPPORT_SELECTION
\r
134 BYTE i = tmp.BlindGetPixelIndex(x,y);
\r
135 if (!bDirection && i<level) BlindSetPixelColor(x,y,nBkgndColor,bSetAlpha);
\r
136 if (bDirection && i>=level) BlindSetPixelColor(x,y,nBkgndColor,bSetAlpha);
\r
143 ////////////////////////////////////////////////////////////////////////////////
\r
145 * Extract RGB channels from the image. Each channel is an 8 bit grayscale image.
\r
146 * \param r,g,b: pointers to CxImage objects, to store the splited channels
\r
147 * \return true if everything is ok
\r
149 bool CxImage::SplitRGB(CxImage* r,CxImage* g,CxImage* b)
\r
151 if (!pDib) return false;
\r
152 if (r==NULL && g==NULL && b==NULL) return false;
\r
154 CxImage tmpr(head.biWidth,head.biHeight,8);
\r
155 CxImage tmpg(head.biWidth,head.biHeight,8);
\r
156 CxImage tmpb(head.biWidth,head.biHeight,8);
\r
159 for(long y=0; y<head.biHeight; y++){
\r
160 for(long x=0; x<head.biWidth; x++){
\r
161 color = BlindGetPixelColor(x,y);
\r
162 if (r) tmpr.BlindSetPixelIndex(x,y,color.rgbRed);
\r
163 if (g) tmpg.BlindSetPixelIndex(x,y,color.rgbGreen);
\r
164 if (b) tmpb.BlindSetPixelIndex(x,y,color.rgbBlue);
\r
168 if (r) tmpr.SetGrayPalette();
\r
169 if (g) tmpg.SetGrayPalette();
\r
170 if (b) tmpb.SetGrayPalette();
\r
172 /*for(long j=0; j<256; j++){
\r
174 if (r) tmpr.SetPaletteColor(i,i,0,0);
\r
175 if (g) tmpg.SetPaletteColor(i,0,i,0);
\r
176 if (b) tmpb.SetPaletteColor(i,0,0,i);
\r
179 if (r) r->Transfer(tmpr);
\r
180 if (g) g->Transfer(tmpg);
\r
181 if (b) b->Transfer(tmpb);
\r
185 ////////////////////////////////////////////////////////////////////////////////
\r
187 * Extract CMYK channels from the image. Each channel is an 8 bit grayscale image.
\r
188 * \param c,m,y,k: pointers to CxImage objects, to store the splited channels
\r
189 * \return true if everything is ok
\r
191 bool CxImage::SplitCMYK(CxImage* c,CxImage* m,CxImage* y,CxImage* k)
\r
193 if (!pDib) return false;
\r
194 if (c==NULL && m==NULL && y==NULL && k==NULL) return false;
\r
196 CxImage tmpc(head.biWidth,head.biHeight,8);
\r
197 CxImage tmpm(head.biWidth,head.biHeight,8);
\r
198 CxImage tmpy(head.biWidth,head.biHeight,8);
\r
199 CxImage tmpk(head.biWidth,head.biHeight,8);
\r
202 for(long yy=0; yy<head.biHeight; yy++){
\r
203 for(long xx=0; xx<head.biWidth; xx++){
\r
204 color = BlindGetPixelColor(xx,yy);
\r
205 if (c) tmpc.BlindSetPixelIndex(xx,yy,(BYTE)(255-color.rgbRed));
\r
206 if (m) tmpm.BlindSetPixelIndex(xx,yy,(BYTE)(255-color.rgbGreen));
\r
207 if (y) tmpy.BlindSetPixelIndex(xx,yy,(BYTE)(255-color.rgbBlue));
\r
208 if (k) tmpk.BlindSetPixelIndex(xx,yy,(BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue));
\r
212 if (c) tmpc.SetGrayPalette();
\r
213 if (m) tmpm.SetGrayPalette();
\r
214 if (y) tmpy.SetGrayPalette();
\r
215 if (k) tmpk.SetGrayPalette();
\r
217 if (c) c->Transfer(tmpc);
\r
218 if (m) m->Transfer(tmpm);
\r
219 if (y) y->Transfer(tmpy);
\r
220 if (k) k->Transfer(tmpk);
\r
224 ////////////////////////////////////////////////////////////////////////////////
\r
226 * Extract YUV channels from the image. Each channel is an 8 bit grayscale image.
\r
227 * \param y,u,v: pointers to CxImage objects, to store the splited channels
\r
228 * \return true if everything is ok
\r
230 bool CxImage::SplitYUV(CxImage* y,CxImage* u,CxImage* v)
\r
232 if (!pDib) return false;
\r
233 if (y==NULL && u==NULL && v==NULL) return false;
\r
235 CxImage tmpy(head.biWidth,head.biHeight,8);
\r
236 CxImage tmpu(head.biWidth,head.biHeight,8);
\r
237 CxImage tmpv(head.biWidth,head.biHeight,8);
\r
240 for(long yy=0; yy<head.biHeight; yy++){
\r
241 for(long x=0; x<head.biWidth; x++){
\r
242 color = RGBtoYUV(BlindGetPixelColor(x,yy));
\r
243 if (y) tmpy.BlindSetPixelIndex(x,yy,color.rgbRed);
\r
244 if (u) tmpu.BlindSetPixelIndex(x,yy,color.rgbGreen);
\r
245 if (v) tmpv.BlindSetPixelIndex(x,yy,color.rgbBlue);
\r
249 if (y) tmpy.SetGrayPalette();
\r
250 if (u) tmpu.SetGrayPalette();
\r
251 if (v) tmpv.SetGrayPalette();
\r
253 if (y) y->Transfer(tmpy);
\r
254 if (u) u->Transfer(tmpu);
\r
255 if (v) v->Transfer(tmpv);
\r
259 ////////////////////////////////////////////////////////////////////////////////
\r
261 * Extract YIQ channels from the image. Each channel is an 8 bit grayscale image.
\r
262 * \param y,i,q: pointers to CxImage objects, to store the splited channels
\r
263 * \return true if everything is ok
\r
265 bool CxImage::SplitYIQ(CxImage* y,CxImage* i,CxImage* q)
\r
267 if (!pDib) return false;
\r
268 if (y==NULL && i==NULL && q==NULL) return false;
\r
270 CxImage tmpy(head.biWidth,head.biHeight,8);
\r
271 CxImage tmpi(head.biWidth,head.biHeight,8);
\r
272 CxImage tmpq(head.biWidth,head.biHeight,8);
\r
275 for(long yy=0; yy<head.biHeight; yy++){
\r
276 for(long x=0; x<head.biWidth; x++){
\r
277 color = RGBtoYIQ(BlindGetPixelColor(x,yy));
\r
278 if (y) tmpy.BlindSetPixelIndex(x,yy,color.rgbRed);
\r
279 if (i) tmpi.BlindSetPixelIndex(x,yy,color.rgbGreen);
\r
280 if (q) tmpq.BlindSetPixelIndex(x,yy,color.rgbBlue);
\r
284 if (y) tmpy.SetGrayPalette();
\r
285 if (i) tmpi.SetGrayPalette();
\r
286 if (q) tmpq.SetGrayPalette();
\r
288 if (y) y->Transfer(tmpy);
\r
289 if (i) i->Transfer(tmpi);
\r
290 if (q) q->Transfer(tmpq);
\r
294 ////////////////////////////////////////////////////////////////////////////////
\r
296 * Extract XYZ channels from the image. Each channel is an 8 bit grayscale image.
\r
297 * \param x,y,z: pointers to CxImage objects, to store the splited channels
\r
298 * \return true if everything is ok
\r
300 bool CxImage::SplitXYZ(CxImage* x,CxImage* y,CxImage* z)
\r
302 if (!pDib) return false;
\r
303 if (x==NULL && y==NULL && z==NULL) return false;
\r
305 CxImage tmpx(head.biWidth,head.biHeight,8);
\r
306 CxImage tmpy(head.biWidth,head.biHeight,8);
\r
307 CxImage tmpz(head.biWidth,head.biHeight,8);
\r
310 for(long yy=0; yy<head.biHeight; yy++){
\r
311 for(long xx=0; xx<head.biWidth; xx++){
\r
312 color = RGBtoXYZ(BlindGetPixelColor(xx,yy));
\r
313 if (x) tmpx.BlindSetPixelIndex(xx,yy,color.rgbRed);
\r
314 if (y) tmpy.BlindSetPixelIndex(xx,yy,color.rgbGreen);
\r
315 if (z) tmpz.BlindSetPixelIndex(xx,yy,color.rgbBlue);
\r
319 if (x) tmpx.SetGrayPalette();
\r
320 if (y) tmpy.SetGrayPalette();
\r
321 if (z) tmpz.SetGrayPalette();
\r
323 if (x) x->Transfer(tmpx);
\r
324 if (y) y->Transfer(tmpy);
\r
325 if (z) z->Transfer(tmpz);
\r
329 ////////////////////////////////////////////////////////////////////////////////
\r
331 * Extract HSL channels from the image. Each channel is an 8 bit grayscale image.
\r
332 * \param h,s,l: pointers to CxImage objects, to store the splited channels
\r
333 * \return true if everything is ok
\r
335 bool CxImage::SplitHSL(CxImage* h,CxImage* s,CxImage* l)
\r
337 if (!pDib) return false;
\r
338 if (h==NULL && s==NULL && l==NULL) return false;
\r
340 CxImage tmph(head.biWidth,head.biHeight,8);
\r
341 CxImage tmps(head.biWidth,head.biHeight,8);
\r
342 CxImage tmpl(head.biWidth,head.biHeight,8);
\r
345 for(long y=0; y<head.biHeight; y++){
\r
346 for(long x=0; x<head.biWidth; x++){
\r
347 color = RGBtoHSL(BlindGetPixelColor(x,y));
\r
348 if (h) tmph.BlindSetPixelIndex(x,y,color.rgbRed);
\r
349 if (s) tmps.BlindSetPixelIndex(x,y,color.rgbGreen);
\r
350 if (l) tmpl.BlindSetPixelIndex(x,y,color.rgbBlue);
\r
354 if (h) tmph.SetGrayPalette();
\r
355 if (s) tmps.SetGrayPalette();
\r
356 if (l) tmpl.SetGrayPalette();
\r
358 /* pseudo-color generator for hue channel (visual debug)
\r
359 if (h) for(long j=0; j<256; j++){
\r
361 RGBQUAD hsl={120,240,i,0};
\r
362 tmph.SetPaletteColor(i,HSLtoRGB(hsl));
\r
365 if (h) h->Transfer(tmph);
\r
366 if (s) s->Transfer(tmps);
\r
367 if (l) l->Transfer(tmpl);
\r
371 ////////////////////////////////////////////////////////////////////////////////
\r
372 #define HSLMAX 255 /* H,L, and S vary over 0-HSLMAX */
\r
373 #define RGBMAX 255 /* R,G, and B vary over 0-RGBMAX */
\r
374 /* HSLMAX BEST IF DIVISIBLE BY 6 */
\r
375 /* RGBMAX, HSLMAX must each fit in a BYTE. */
\r
376 /* Hue is undefined if Saturation is 0 (grey-scale) */
\r
377 /* This value determines where the Hue scrollbar is */
\r
378 /* initially set for achromatic colors */
\r
379 #define HSLUNDEFINED (HSLMAX*2/3)
\r
380 ////////////////////////////////////////////////////////////////////////////////
\r
381 RGBQUAD CxImage::RGBtoHSL(RGBQUAD lRGBColor)
\r
383 BYTE R,G,B; /* input RGB values */
\r
384 BYTE H,L,S; /* output HSL values */
\r
385 BYTE cMax,cMin; /* max and min RGB values */
\r
386 WORD Rdelta,Gdelta,Bdelta; /* intermediate value: % of spread from max*/
\r
388 R = lRGBColor.rgbRed; /* get R, G, and B out of DWORD */
\r
389 G = lRGBColor.rgbGreen;
\r
390 B = lRGBColor.rgbBlue;
\r
392 cMax = max( max(R,G), B); /* calculate lightness */
\r
393 cMin = min( min(R,G), B);
\r
394 L = (BYTE)((((cMax+cMin)*HSLMAX)+RGBMAX)/(2*RGBMAX));
\r
396 if (cMax==cMin){ /* r=g=b --> achromatic case */
\r
397 S = 0; /* saturation */
\r
398 H = HSLUNDEFINED; /* hue */
\r
399 } else { /* chromatic case */
\r
400 if (L <= (HSLMAX/2)) /* saturation */
\r
401 S = (BYTE)((((cMax-cMin)*HSLMAX)+((cMax+cMin)/2))/(cMax+cMin));
\r
403 S = (BYTE)((((cMax-cMin)*HSLMAX)+((2*RGBMAX-cMax-cMin)/2))/(2*RGBMAX-cMax-cMin));
\r
405 Rdelta = (WORD)((((cMax-R)*(HSLMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin));
\r
406 Gdelta = (WORD)((((cMax-G)*(HSLMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin));
\r
407 Bdelta = (WORD)((((cMax-B)*(HSLMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin));
\r
410 H = (BYTE)(Bdelta - Gdelta);
\r
411 else if (G == cMax)
\r
412 H = (BYTE)((HSLMAX/3) + Rdelta - Bdelta);
\r
413 else /* B == cMax */
\r
414 H = (BYTE)(((2*HSLMAX)/3) + Gdelta - Rdelta);
\r
416 // if (H < 0) H += HSLMAX; //always false
\r
417 if (H > HSLMAX) H -= HSLMAX;
\r
419 RGBQUAD hsl={L,S,H,0};
\r
422 ////////////////////////////////////////////////////////////////////////////////
\r
423 float CxImage::HueToRGB(float n1,float n2, float hue)
\r
425 //<F. Livraghi> fixed implementation for HSL2RGB routine
\r
434 rValue = n1 + (n2-n1)*hue/60.0f;
\r
435 else if (hue < 180)
\r
437 else if (hue < 240)
\r
438 rValue = n1+(n2-n1)*(240-hue)/60;
\r
444 ////////////////////////////////////////////////////////////////////////////////
\r
445 RGBQUAD CxImage::HSLtoRGB(COLORREF cHSLColor)
\r
447 return HSLtoRGB(RGBtoRGBQUAD(cHSLColor));
\r
449 ////////////////////////////////////////////////////////////////////////////////
\r
450 RGBQUAD CxImage::HSLtoRGB(RGBQUAD lHSLColor)
\r
452 //<F. Livraghi> fixed implementation for HSL2RGB routine
\r
457 h = (float)lHSLColor.rgbRed * 360.0f/255.0f;
\r
458 s = (float)lHSLColor.rgbGreen/255.0f;
\r
459 l = (float)lHSLColor.rgbBlue/255.0f;
\r
461 if (l <= 0.5) m2 = l * (1+s);
\r
462 else m2 = l + s - l*s;
\r
467 r=g=b=(BYTE)(l*255.0f);
\r
469 r = (BYTE)(HueToRGB(m1,m2,h+120) * 255.0f);
\r
470 g = (BYTE)(HueToRGB(m1,m2,h) * 255.0f);
\r
471 b = (BYTE)(HueToRGB(m1,m2,h-120) * 255.0f);
\r
474 RGBQUAD rgb = {b,g,r,0};
\r
477 ////////////////////////////////////////////////////////////////////////////////
\r
478 RGBQUAD CxImage::YUVtoRGB(RGBQUAD lYUVColor)
\r
481 float Y = lYUVColor.rgbRed;
\r
482 U = lYUVColor.rgbGreen - 128;
\r
483 V = lYUVColor.rgbBlue - 128;
\r
485 // R = (int)(1.164 * Y + 2.018 * U);
\r
486 // G = (int)(1.164 * Y - 0.813 * V - 0.391 * U);
\r
487 // B = (int)(1.164 * Y + 1.596 * V);
\r
488 R = (int)( Y + 1.403f * V);
\r
489 G = (int)( Y - 0.344f * U - 0.714f * V);
\r
490 B = (int)( Y + 1.770f * U);
\r
492 R= min(255,max(0,R));
\r
493 G= min(255,max(0,G));
\r
494 B= min(255,max(0,B));
\r
495 RGBQUAD rgb={(BYTE)B,(BYTE)G,(BYTE)R,0};
\r
498 ////////////////////////////////////////////////////////////////////////////////
\r
499 RGBQUAD CxImage::RGBtoYUV(RGBQUAD lRGBColor)
\r
502 R = lRGBColor.rgbRed;
\r
503 G = lRGBColor.rgbGreen;
\r
504 B = lRGBColor.rgbBlue;
\r
506 // Y = (int)( 0.257 * R + 0.504 * G + 0.098 * B);
\r
507 // U = (int)( 0.439 * R - 0.368 * G - 0.071 * B + 128);
\r
508 // V = (int)(-0.148 * R - 0.291 * G + 0.439 * B + 128);
\r
509 Y = (int)(0.299f * R + 0.587f * G + 0.114f * B);
\r
510 U = (int)((B-Y) * 0.565f + 128);
\r
511 V = (int)((R-Y) * 0.713f + 128);
\r
513 Y= min(255,max(0,Y));
\r
514 U= min(255,max(0,U));
\r
515 V= min(255,max(0,V));
\r
516 RGBQUAD yuv={(BYTE)V,(BYTE)U,(BYTE)Y,0};
\r
519 ////////////////////////////////////////////////////////////////////////////////
\r
520 RGBQUAD CxImage::YIQtoRGB(RGBQUAD lYIQColor)
\r
523 float Y = lYIQColor.rgbRed;
\r
524 I = lYIQColor.rgbGreen - 128;
\r
525 Q = lYIQColor.rgbBlue - 128;
\r
527 R = (int)( Y + 0.956f * I + 0.621f * Q);
\r
528 G = (int)( Y - 0.273f * I - 0.647f * Q);
\r
529 B = (int)( Y - 1.104f * I + 1.701f * Q);
\r
531 R= min(255,max(0,R));
\r
532 G= min(255,max(0,G));
\r
533 B= min(255,max(0,B));
\r
534 RGBQUAD rgb={(BYTE)B,(BYTE)G,(BYTE)R,0};
\r
537 ////////////////////////////////////////////////////////////////////////////////
\r
538 RGBQUAD CxImage::RGBtoYIQ(RGBQUAD lRGBColor)
\r
541 R = lRGBColor.rgbRed;
\r
542 G = lRGBColor.rgbGreen;
\r
543 B = lRGBColor.rgbBlue;
\r
545 Y = (int)( 0.2992f * R + 0.5868f * G + 0.1140f * B);
\r
546 I = (int)( 0.5960f * R - 0.2742f * G - 0.3219f * B + 128);
\r
547 Q = (int)( 0.2109f * R - 0.5229f * G + 0.3120f * B + 128);
\r
549 Y= min(255,max(0,Y));
\r
550 I= min(255,max(0,I));
\r
551 Q= min(255,max(0,Q));
\r
552 RGBQUAD yiq={(BYTE)Q,(BYTE)I,(BYTE)Y,0};
\r
555 ////////////////////////////////////////////////////////////////////////////////
\r
556 RGBQUAD CxImage::XYZtoRGB(RGBQUAD lXYZColor)
\r
559 X = lXYZColor.rgbRed;
\r
560 Y = lXYZColor.rgbGreen;
\r
561 Z = lXYZColor.rgbBlue;
\r
564 R = (int)( 3.240479f * X - 1.537150f * Y - 0.498535f * Z * k);
\r
565 G = (int)( -0.969256f * X + 1.875992f * Y + 0.041556f * Z * k);
\r
566 B = (int)( 0.055648f * X - 0.204043f * Y + 1.057311f * Z * k);
\r
568 R= min(255,max(0,R));
\r
569 G= min(255,max(0,G));
\r
570 B= min(255,max(0,B));
\r
571 RGBQUAD rgb={(BYTE)B,(BYTE)G,(BYTE)R,0};
\r
574 ////////////////////////////////////////////////////////////////////////////////
\r
575 RGBQUAD CxImage::RGBtoXYZ(RGBQUAD lRGBColor)
\r
578 R = lRGBColor.rgbRed;
\r
579 G = lRGBColor.rgbGreen;
\r
580 B = lRGBColor.rgbBlue;
\r
582 X = (int)( 0.412453f * R + 0.357580f * G + 0.180423f * B);
\r
583 Y = (int)( 0.212671f * R + 0.715160f * G + 0.072169f * B);
\r
584 Z = (int)((0.019334f * R + 0.119193f * G + 0.950227f * B)*0.918483657f);
\r
586 //X= min(255,max(0,X));
\r
587 //Y= min(255,max(0,Y));
\r
588 //Z= min(255,max(0,Z));
\r
589 RGBQUAD xyz={(BYTE)Z,(BYTE)Y,(BYTE)X,0};
\r
592 ////////////////////////////////////////////////////////////////////////////////
\r
594 * Generates a "rainbow" palette with saturated colors
\r
595 * \param correction: 1 generates a single hue spectrum. 0.75 is nice for scientific applications.
\r
597 void CxImage::HuePalette(float correction)
\r
599 if (head.biClrUsed==0) return;
\r
601 for(DWORD j=0; j<head.biClrUsed; j++){
\r
602 BYTE i=(BYTE)(j*correction*(255/(head.biClrUsed-1)));
\r
603 RGBQUAD hsl={120,240,i,0};
\r
604 SetPaletteColor((BYTE)j,HSLtoRGB(hsl));
\r
607 ////////////////////////////////////////////////////////////////////////////////
\r
609 * Replaces the original hue and saturation values.
\r
611 * \param sat: saturation
\r
612 * \param blend: can be from 0 (no effect) to 1 (full effect)
\r
613 * \return true if everything is ok
\r
615 bool CxImage::Colorize(BYTE hue, BYTE sat, float blend)
\r
617 if (!pDib) return false;
\r
619 if (blend < 0.0f) blend = 0.0f;
\r
620 if (blend > 1.0f) blend = 1.0f;
\r
621 int a0 = (int)(256*blend);
\r
624 bool bFullBlend = false;
\r
625 if (blend > 0.999f) bFullBlend = true;
\r
628 if (head.biClrUsed==0){
\r
630 long xmin,xmax,ymin,ymax;
\r
632 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
633 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
636 xmax = head.biWidth; ymax=head.biHeight;
\r
639 for(long y=ymin; y<ymax; y++){
\r
640 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
641 if (info.nEscape) break;
\r
642 for(long x=xmin; x<xmax; x++){
\r
643 #if CXIMAGE_SUPPORT_SELECTION
\r
644 if (BlindSelectionIsInside(x,y))
\r
645 #endif //CXIMAGE_SUPPORT_SELECTION
\r
648 color = RGBtoHSL(BlindGetPixelColor(x,y));
\r
650 color.rgbGreen=sat;
\r
651 BlindSetPixelColor(x,y,HSLtoRGB(color));
\r
653 color = BlindGetPixelColor(x,y);
\r
656 hsl.rgbBlue = (BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue);
\r
657 hsl = HSLtoRGB(hsl);
\r
658 //BlendPixelColor(x,y,hsl,blend);
\r
659 //color.rgbRed = (BYTE)(hsl.rgbRed * blend + color.rgbRed * (1.0f - blend));
\r
660 //color.rgbBlue = (BYTE)(hsl.rgbBlue * blend + color.rgbBlue * (1.0f - blend));
\r
661 //color.rgbGreen = (BYTE)(hsl.rgbGreen * blend + color.rgbGreen * (1.0f - blend));
\r
662 color.rgbRed = (BYTE)((hsl.rgbRed * a0 + color.rgbRed * a1)>>8);
\r
663 color.rgbBlue = (BYTE)((hsl.rgbBlue * a0 + color.rgbBlue * a1)>>8);
\r
664 color.rgbGreen = (BYTE)((hsl.rgbGreen * a0 + color.rgbGreen * a1)>>8);
\r
665 BlindSetPixelColor(x,y,color);
\r
671 for(DWORD j=0; j<head.biClrUsed; j++){
\r
673 color = RGBtoHSL(GetPaletteColor((BYTE)j));
\r
675 color.rgbGreen=sat;
\r
676 SetPaletteColor((BYTE)j,HSLtoRGB(color));
\r
678 color = GetPaletteColor((BYTE)j);
\r
681 hsl.rgbBlue = (BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue);
\r
682 hsl = HSLtoRGB(hsl);
\r
683 color.rgbRed = (BYTE)(hsl.rgbRed * blend + color.rgbRed * (1.0f - blend));
\r
684 color.rgbBlue = (BYTE)(hsl.rgbBlue * blend + color.rgbBlue * (1.0f - blend));
\r
685 color.rgbGreen = (BYTE)(hsl.rgbGreen * blend + color.rgbGreen * (1.0f - blend));
\r
686 SetPaletteColor((BYTE)j,color);
\r
693 ////////////////////////////////////////////////////////////////////////////////
\r
695 * Changes the brightness and the contrast of the image.
\r
696 * \param brightness: can be from -255 to 255, if brightness is negative, the image becomes dark.
\r
697 * \param contrast: can be from -100 to 100, the neutral value is 0.
\r
698 * \return true if everything is ok
\r
700 bool CxImage::Light(long brightness, long contrast)
\r
702 if (!pDib) return false;
\r
703 float c=(100 + contrast)/100.0f;
\r
706 BYTE cTable[256]; //<nipper>
\r
707 for (int i=0;i<256;i++) {
\r
708 cTable[i] = (BYTE)max(0,min(255,(int)((i-128)*c + brightness + 0.5f)));
\r
711 return Lut(cTable);
\r
713 ////////////////////////////////////////////////////////////////////////////////
\r
715 * \return mean lightness of the image. Useful with Threshold() and Light()
\r
717 float CxImage::Mean()
\r
719 if (!pDib) return 0;
\r
721 CxImage tmp(*this,true);
\r
722 if (!tmp.IsValid()){
\r
723 strcpy(info.szLastError,tmp.GetLastError());
\r
730 long xmin,xmax,ymin,ymax;
\r
732 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
733 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
736 xmax = head.biWidth; ymax=head.biHeight;
\r
738 if (xmin==xmax || ymin==ymax) return (float)0.0;
\r
740 BYTE *iSrc=tmp.info.pImage;
\r
741 iSrc += tmp.info.dwEffWidth*ymin; // necessary for selections <Admir Hodzic>
\r
743 for(long y=ymin; y<ymax; y++){
\r
744 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin)); //<zhanghk><Anatoly Ivasyuk>
\r
745 for(long x=xmin; x<xmax; x++){
\r
748 iSrc+=tmp.info.dwEffWidth;
\r
750 return sum/(xmax-xmin)/(ymax-ymin);
\r
752 ////////////////////////////////////////////////////////////////////////////////
\r
755 * \param kernel: convolving matrix, in row format.
\r
756 * \param Ksize: size of the kernel.
\r
757 * \param Kfactor: normalization constant.
\r
758 * \param Koffset: bias.
\r
759 * \verbatim Example: the "soften" filter uses this kernel:
\r
763 the function needs: kernel={1,1,1,1,8,1,1,1,1}; Ksize=3; Kfactor=16; Koffset=0; \endverbatim
\r
764 * \return true if everything is ok
\r
766 bool CxImage::Filter(long* kernel, long Ksize, long Kfactor, long Koffset)
\r
768 if (!pDib) return false;
\r
771 long kmax= Ksize-k2;
\r
773 long ksumcur,ksumtot;
\r
776 CxImage tmp(*this);
\r
777 if (!tmp.IsValid()){
\r
778 strcpy(info.szLastError,tmp.GetLastError());
\r
782 long xmin,xmax,ymin,ymax;
\r
784 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
785 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
788 xmax = head.biWidth; ymax=head.biHeight;
\r
792 for(long j=-k2;j<kmax;j++){
\r
793 for(long k=-k2;k<kmax;k++){
\r
794 ksumtot += kernel[(j+k2)+Ksize*(k+k2)];
\r
798 if ((head.biBitCount==8) && IsGrayScale())
\r
800 unsigned char* cPtr;
\r
801 unsigned char* cPtr2;
\r
804 cPtr = info.pImage;
\r
805 cPtr2 = (unsigned char *)tmp.info.pImage;
\r
806 for(long y=ymin; y<ymax; y++){
\r
807 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
808 if (info.nEscape) break;
\r
809 iY1 = y*info.dwEffWidth+xmin;
\r
810 for(long x=xmin; x<xmax; x++, iY1++){
\r
811 #if CXIMAGE_SUPPORT_SELECTION
\r
812 if (BlindSelectionIsInside(x,y))
\r
813 #endif //CXIMAGE_SUPPORT_SELECTION
\r
817 iY2 = ((y-k2)*info.dwEffWidth);
\r
818 for(long j=-k2;j<kmax;j++, iY2+=info.dwEffWidth)
\r
820 if (0>(y+j) || (y+j)>=head.biHeight) continue;
\r
822 for(long k=-k2;k<kmax;k++, iCount++)
\r
824 if (0>(x+k) || (x+k)>=head.biWidth) continue;
\r
826 b += cPtr[iY+k] * i;
\r
830 if (Kfactor==0 || ksumcur==0){
\r
831 cPtr2[iY1] = (BYTE)min(255, max(0,(int)(b + Koffset)));
\r
832 } else if (ksumtot == ksumcur) {
\r
833 cPtr2[iY1] = (BYTE)min(255, max(0,(int)(b/Kfactor + Koffset)));
\r
835 cPtr2[iY1] = (BYTE)min(255, max(0,(int)((b*ksumtot)/(ksumcur*Kfactor) + Koffset)));
\r
843 for(long y=ymin; y<ymax; y++){
\r
844 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
845 if (info.nEscape) break;
\r
846 for(long x=xmin; x<xmax; x++){
\r
847 #if CXIMAGE_SUPPORT_SELECTION
\r
848 if (BlindSelectionIsInside(x,y))
\r
849 #endif //CXIMAGE_SUPPORT_SELECTION
\r
852 for(long j=-k2;j<kmax;j++){
\r
853 for(long k=-k2;k<kmax;k++){
\r
854 if (!IsInside(x+j,y+k)) continue;
\r
855 c = BlindGetPixelColor(x+j,y+k);
\r
856 i = kernel[(j+k2)+Ksize*(k+k2)];
\r
858 g += c.rgbGreen * i;
\r
859 b += c.rgbBlue * i;
\r
863 if (Kfactor==0 || ksumcur==0){
\r
864 c.rgbRed = (BYTE)min(255, max(0,(int)(r + Koffset)));
\r
865 c.rgbGreen = (BYTE)min(255, max(0,(int)(g + Koffset)));
\r
866 c.rgbBlue = (BYTE)min(255, max(0,(int)(b + Koffset)));
\r
867 } else if (ksumtot == ksumcur) {
\r
868 c.rgbRed = (BYTE)min(255, max(0,(int)(r/Kfactor + Koffset)));
\r
869 c.rgbGreen = (BYTE)min(255, max(0,(int)(g/Kfactor + Koffset)));
\r
870 c.rgbBlue = (BYTE)min(255, max(0,(int)(b/Kfactor + Koffset)));
\r
872 c.rgbRed = (BYTE)min(255, max(0,(int)((r*ksumtot)/(ksumcur*Kfactor) + Koffset)));
\r
873 c.rgbGreen = (BYTE)min(255, max(0,(int)((g*ksumtot)/(ksumcur*Kfactor) + Koffset)));
\r
874 c.rgbBlue = (BYTE)min(255, max(0,(int)((b*ksumtot)/(ksumcur*Kfactor) + Koffset)));
\r
876 tmp.BlindSetPixelColor(x,y,c);
\r
884 ////////////////////////////////////////////////////////////////////////////////
\r
886 * Enhance the dark areas of the image
\r
887 * \param Ksize: size of the kernel.
\r
888 * \return true if everything is ok
\r
890 bool CxImage::Erode(long Ksize)
\r
892 if (!pDib) return false;
\r
895 long kmax= Ksize-k2;
\r
899 CxImage tmp(*this);
\r
900 if (!tmp.IsValid()){
\r
901 strcpy(info.szLastError,tmp.GetLastError());
\r
905 long xmin,xmax,ymin,ymax;
\r
907 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
908 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
911 xmax = head.biWidth; ymax=head.biHeight;
\r
914 for(long y=ymin; y<ymax; y++){
\r
915 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
916 if (info.nEscape) break;
\r
917 for(long x=xmin; x<xmax; x++){
\r
918 #if CXIMAGE_SUPPORT_SELECTION
\r
919 if (BlindSelectionIsInside(x,y))
\r
920 #endif //CXIMAGE_SUPPORT_SELECTION
\r
923 for(long j=-k2;j<kmax;j++){
\r
924 for(long k=-k2;k<kmax;k++){
\r
925 if (!IsInside(x+j,y+k)) continue;
\r
926 c = BlindGetPixelColor(x+j,y+k);
\r
927 if (c.rgbRed < r) r=c.rgbRed;
\r
928 if (c.rgbGreen < g) g=c.rgbGreen;
\r
929 if (c.rgbBlue < b) b=c.rgbBlue;
\r
935 tmp.BlindSetPixelColor(x,y,c);
\r
942 ////////////////////////////////////////////////////////////////////////////////
\r
944 * Enhance the light areas of the image
\r
945 * \param Ksize: size of the kernel.
\r
946 * \return true if everything is ok
\r
948 bool CxImage::Dilate(long Ksize)
\r
950 if (!pDib) return false;
\r
953 long kmax= Ksize-k2;
\r
957 CxImage tmp(*this);
\r
958 if (!tmp.IsValid()){
\r
959 strcpy(info.szLastError,tmp.GetLastError());
\r
963 long xmin,xmax,ymin,ymax;
\r
965 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
966 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
969 xmax = head.biWidth; ymax=head.biHeight;
\r
972 for(long y=ymin; y<ymax; y++){
\r
973 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
974 if (info.nEscape) break;
\r
975 for(long x=xmin; x<xmax; x++){
\r
976 #if CXIMAGE_SUPPORT_SELECTION
\r
977 if (BlindSelectionIsInside(x,y))
\r
978 #endif //CXIMAGE_SUPPORT_SELECTION
\r
981 for(long j=-k2;j<kmax;j++){
\r
982 for(long k=-k2;k<kmax;k++){
\r
983 if (!IsInside(x+j,y+k)) continue;
\r
984 c = BlindGetPixelColor(x+j,y+k);
\r
985 if (c.rgbRed > r) r=c.rgbRed;
\r
986 if (c.rgbGreen > g) g=c.rgbGreen;
\r
987 if (c.rgbBlue > b) b=c.rgbBlue;
\r
993 tmp.BlindSetPixelColor(x,y,c);
\r
1000 ////////////////////////////////////////////////////////////////////////////////
\r
1002 * Enhance the variations between adjacent pixels.
\r
1003 * Similar results can be achieved using Filter(),
\r
1004 * but the algorithms are different both in Edge() and in Contour().
\r
1005 * \param Ksize: size of the kernel.
\r
1006 * \return true if everything is ok
\r
1008 bool CxImage::Edge(long Ksize)
\r
1010 if (!pDib) return false;
\r
1012 long k2 = Ksize/2;
\r
1013 long kmax= Ksize-k2;
\r
1014 BYTE r,g,b,rr,gg,bb;
\r
1017 CxImage tmp(*this);
\r
1018 if (!tmp.IsValid()){
\r
1019 strcpy(info.szLastError,tmp.GetLastError());
\r
1023 long xmin,xmax,ymin,ymax;
\r
1025 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
1026 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
1029 xmax = head.biWidth; ymax=head.biHeight;
\r
1032 for(long y=ymin; y<ymax; y++){
\r
1033 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
1034 if (info.nEscape) break;
\r
1035 for(long x=xmin; x<xmax; x++){
\r
1036 #if CXIMAGE_SUPPORT_SELECTION
\r
1037 if (BlindSelectionIsInside(x,y))
\r
1038 #endif //CXIMAGE_SUPPORT_SELECTION
\r
1042 for(long j=-k2;j<kmax;j++){
\r
1043 for(long k=-k2;k<kmax;k++){
\r
1044 if (!IsInside(x+j,y+k)) continue;
\r
1045 c = BlindGetPixelColor(x+j,y+k);
\r
1046 if (c.rgbRed > r) r=c.rgbRed;
\r
1047 if (c.rgbGreen > g) g=c.rgbGreen;
\r
1048 if (c.rgbBlue > b) b=c.rgbBlue;
\r
1050 if (c.rgbRed < rr) rr=c.rgbRed;
\r
1051 if (c.rgbGreen < gg) gg=c.rgbGreen;
\r
1052 if (c.rgbBlue < bb) bb=c.rgbBlue;
\r
1055 c.rgbRed = (BYTE)(255-abs(r-rr));
\r
1056 c.rgbGreen = (BYTE)(255-abs(g-gg));
\r
1057 c.rgbBlue = (BYTE)(255-abs(b-bb));
\r
1058 tmp.BlindSetPixelColor(x,y,c);
\r
1065 ////////////////////////////////////////////////////////////////////////////////
\r
1067 * Blends two images
\r
1068 * \param imgsrc2: image to be mixed with this
\r
1069 * \param op: blending method; see ImageOpType
\r
1070 * \param lXOffset, lYOffset: image displacement
\r
1071 * \param bMixAlpha: if true and imgsrc2 has a valid alpha layer, it will be mixed in the destination image.
\r
1072 * \return true if everything is ok
\r
1074 * thanks to Mwolski
\r
1077 void CxImage::Mix(CxImage & imgsrc2, ImageOpType op, long lXOffset, long lYOffset, bool bMixAlpha)
\r
1079 long lWide = min(GetWidth(),imgsrc2.GetWidth()-lXOffset);
\r
1080 long lHeight = min(GetHeight(),imgsrc2.GetHeight()-lYOffset);
\r
1082 bool bEditAlpha = imgsrc2.AlphaIsValid() & bMixAlpha;
\r
1084 if (bEditAlpha && AlphaIsValid()==false){
\r
1088 RGBQUAD rgbBackgrnd1 = GetTransColor();
\r
1089 RGBQUAD rgb1, rgb2, rgbDest;
\r
1091 for(long lY=0;lY<lHeight;lY++)
\r
1093 info.nProgress = (long)(100*lY/head.biHeight);
\r
1094 if (info.nEscape) break;
\r
1096 for(long lX=0;lX<lWide;lX++)
\r
1098 #if CXIMAGE_SUPPORT_SELECTION
\r
1099 if (SelectionIsInside(lX,lY) && imgsrc2.SelectionIsInside(lX+lXOffset,lY+lYOffset))
\r
1100 #endif //CXIMAGE_SUPPORT_SELECTION
\r
1102 rgb1 = GetPixelColor(lX,lY);
\r
1103 rgb2 = imgsrc2.GetPixelColor(lX+lXOffset,lY+lYOffset);
\r
1107 rgbDest.rgbBlue = (BYTE)((rgb1.rgbBlue+rgb2.rgbBlue)/2);
\r
1108 rgbDest.rgbGreen = (BYTE)((rgb1.rgbGreen+rgb2.rgbGreen)/2);
\r
1109 rgbDest.rgbRed = (BYTE)((rgb1.rgbRed+rgb2.rgbRed)/2);
\r
1110 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)((rgb1.rgbReserved+rgb2.rgbReserved)/2);
\r
1113 rgbDest.rgbBlue = (BYTE)max(0,min(255,rgb1.rgbBlue+rgb2.rgbBlue));
\r
1114 rgbDest.rgbGreen = (BYTE)max(0,min(255,rgb1.rgbGreen+rgb2.rgbGreen));
\r
1115 rgbDest.rgbRed = (BYTE)max(0,min(255,rgb1.rgbRed+rgb2.rgbRed));
\r
1116 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)max(0,min(255,rgb1.rgbReserved+rgb2.rgbReserved));
\r
1119 rgbDest.rgbBlue = (BYTE)max(0,min(255,rgb1.rgbBlue-rgb2.rgbBlue));
\r
1120 rgbDest.rgbGreen = (BYTE)max(0,min(255,rgb1.rgbGreen-rgb2.rgbGreen));
\r
1121 rgbDest.rgbRed = (BYTE)max(0,min(255,rgb1.rgbRed-rgb2.rgbRed));
\r
1122 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)max(0,min(255,rgb1.rgbReserved-rgb2.rgbReserved));
\r
1125 rgbDest.rgbBlue = (BYTE)(rgb1.rgbBlue&rgb2.rgbBlue);
\r
1126 rgbDest.rgbGreen = (BYTE)(rgb1.rgbGreen&rgb2.rgbGreen);
\r
1127 rgbDest.rgbRed = (BYTE)(rgb1.rgbRed&rgb2.rgbRed);
\r
1128 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)(rgb1.rgbReserved&rgb2.rgbReserved);
\r
1131 rgbDest.rgbBlue = (BYTE)(rgb1.rgbBlue^rgb2.rgbBlue);
\r
1132 rgbDest.rgbGreen = (BYTE)(rgb1.rgbGreen^rgb2.rgbGreen);
\r
1133 rgbDest.rgbRed = (BYTE)(rgb1.rgbRed^rgb2.rgbRed);
\r
1134 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)(rgb1.rgbReserved^rgb2.rgbReserved);
\r
1137 rgbDest.rgbBlue = (BYTE)(rgb1.rgbBlue|rgb2.rgbBlue);
\r
1138 rgbDest.rgbGreen = (BYTE)(rgb1.rgbGreen|rgb2.rgbGreen);
\r
1139 rgbDest.rgbRed = (BYTE)(rgb1.rgbRed|rgb2.rgbRed);
\r
1140 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)(rgb1.rgbReserved|rgb2.rgbReserved);
\r
1143 if(rgb2.rgbBlue==0 && rgb2.rgbGreen==0 && rgb2.rgbRed==0)
\r
1144 rgbDest = rgbBackgrnd1;
\r
1149 if(IsTransparent(lX,lY))
\r
1151 else // copy straight over
\r
1155 if(imgsrc2.IsTransparent(lX+lXOffset,lY+lYOffset))
\r
1157 else // copy straight over
\r
1164 if (imgsrc2.IsTransparent(lX+lXOffset,lY+lYOffset)){
\r
1166 } else if (imgsrc2.AlphaIsValid()){
\r
1167 a=imgsrc2.AlphaGet(lX+lXOffset,lY+lYOffset);
\r
1168 a =(BYTE)((a*imgsrc2.info.nAlphaMax)/255);
\r
1173 if (a==0){ //transparent
\r
1175 } else if (a==255){ //opaque
\r
1179 rgbDest.rgbBlue = (BYTE)((rgb1.rgbBlue*a1+rgb2.rgbBlue*a)/255);
\r
1180 rgbDest.rgbGreen = (BYTE)((rgb1.rgbGreen*a1+rgb2.rgbGreen*a)/255);
\r
1181 rgbDest.rgbRed = (BYTE)((rgb1.rgbRed*a1+rgb2.rgbRed*a)/255);
\r
1184 if (bEditAlpha) rgbDest.rgbReserved = (BYTE)((rgb1.rgbReserved*a)/255);
\r
1188 if(IsTransparent(lX,lY))
\r
1192 long lBDiff = abs(rgb1.rgbBlue - rgbBackgrnd1.rgbBlue);
\r
1193 long lGDiff = abs(rgb1.rgbGreen - rgbBackgrnd1.rgbGreen);
\r
1194 long lRDiff = abs(rgb1.rgbRed - rgbBackgrnd1.rgbRed);
\r
1196 double lAverage = (lBDiff+lGDiff+lRDiff)/3;
\r
1197 double lThresh = 16;
\r
1198 double dLarge = lAverage/lThresh;
\r
1199 double dSmall = (lThresh-lAverage)/lThresh;
\r
1200 double dSmallAmt = dSmall*((double)rgb2.rgbBlue);
\r
1202 if( lAverage < lThresh+1){
\r
1203 rgbDest.rgbBlue = (BYTE)max(0,min(255,(int)(dLarge*((double)rgb1.rgbBlue) +
\r
1205 rgbDest.rgbGreen = (BYTE)max(0,min(255,(int)(dLarge*((double)rgb1.rgbGreen) +
\r
1207 rgbDest.rgbRed = (BYTE)max(0,min(255,(int)(dLarge*((double)rgb1.rgbRed) +
\r
1217 SetPixelColor(lX,lY,rgbDest,bEditAlpha);
\r
1222 ////////////////////////////////////////////////////////////////////////////////
\r
1223 // thanks to Kenneth Ballard
\r
1224 void CxImage::MixFrom(CxImage & imagesrc2, long lXOffset, long lYOffset)
\r
1226 long width = imagesrc2.GetWidth();
\r
1227 long height = imagesrc2.GetHeight();
\r
1231 if (imagesrc2.IsTransparent()) {
\r
1232 for(x = 0; x < width; x++) {
\r
1233 for(y = 0; y < height; y++) {
\r
1234 if(!imagesrc2.IsTransparent(x,y)){
\r
1235 SetPixelColor(x + lXOffset, y + lYOffset, imagesrc2.BlindGetPixelColor(x, y));
\r
1239 } else { //no transparency so just set it <Matt>
\r
1240 for(x = 0; x < width; x++) {
\r
1241 for(y = 0; y < height; y++) {
\r
1242 SetPixelColor(x + lXOffset, y + lYOffset, imagesrc2.BlindGetPixelColor(x, y));
\r
1247 ////////////////////////////////////////////////////////////////////////////////
\r
1249 * Adjusts separately the red, green, and blue values in the image.
\r
1250 * \param r, g, b: can be from -255 to +255.
\r
1251 * \return true if everything is ok
\r
1253 bool CxImage::ShiftRGB(long r, long g, long b)
\r
1255 if (!pDib) return false;
\r
1257 if (head.biClrUsed==0){
\r
1259 long xmin,xmax,ymin,ymax;
\r
1261 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
1262 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
1265 xmax = head.biWidth; ymax=head.biHeight;
\r
1268 for(long y=ymin; y<ymax; y++){
\r
1269 for(long x=xmin; x<xmax; x++){
\r
1270 #if CXIMAGE_SUPPORT_SELECTION
\r
1271 if (BlindSelectionIsInside(x,y))
\r
1272 #endif //CXIMAGE_SUPPORT_SELECTION
\r
1274 color = BlindGetPixelColor(x,y);
\r
1275 color.rgbRed = (BYTE)max(0,min(255,(int)(color.rgbRed + r)));
\r
1276 color.rgbGreen = (BYTE)max(0,min(255,(int)(color.rgbGreen + g)));
\r
1277 color.rgbBlue = (BYTE)max(0,min(255,(int)(color.rgbBlue + b)));
\r
1278 BlindSetPixelColor(x,y,color);
\r
1283 for(DWORD j=0; j<head.biClrUsed; j++){
\r
1284 color = GetPaletteColor((BYTE)j);
\r
1285 color.rgbRed = (BYTE)max(0,min(255,(int)(color.rgbRed + r)));
\r
1286 color.rgbGreen = (BYTE)max(0,min(255,(int)(color.rgbGreen + g)));
\r
1287 color.rgbBlue = (BYTE)max(0,min(255,(int)(color.rgbBlue + b)));
\r
1288 SetPaletteColor((BYTE)j,color);
\r
1293 ////////////////////////////////////////////////////////////////////////////////
\r
1295 * Adjusts the color balance of the image
\r
1296 * \param gamma can be from 0.1 to 5.
\r
1297 * \return true if everything is ok
\r
1300 bool CxImage::Gamma(float gamma)
\r
1302 if (!pDib) return false;
\r
1304 if (gamma <= 0.0f) return false;
\r
1306 double dinvgamma = 1/gamma;
\r
1307 double dMax = pow(255.0, dinvgamma) / 255.0;
\r
1309 BYTE cTable[256]; //<nipper>
\r
1310 for (int i=0;i<256;i++) {
\r
1311 cTable[i] = (BYTE)max(0,min(255,(int)( pow((double)i, dinvgamma) / dMax)));
\r
1314 return Lut(cTable);
\r
1316 ////////////////////////////////////////////////////////////////////////////////
\r
1318 * Adjusts the color balance indipendent for each color channel
\r
1319 * \param gammaR, gammaG, gammaB can be from 0.1 to 5.
\r
1320 * \return true if everything is ok
\r
1323 bool CxImage::GammaRGB(float gammaR, float gammaG, float gammaB)
\r
1325 if (!pDib) return false;
\r
1327 if (gammaR <= 0.0f) return false;
\r
1328 if (gammaG <= 0.0f) return false;
\r
1329 if (gammaB <= 0.0f) return false;
\r
1331 double dinvgamma, dMax;
\r
1334 dinvgamma = 1/gammaR;
\r
1335 dMax = pow(255.0, dinvgamma) / 255.0;
\r
1336 BYTE cTableR[256];
\r
1337 for (i=0;i<256;i++) {
\r
1338 cTableR[i] = (BYTE)max(0,min(255,(int)( pow((double)i, dinvgamma) / dMax)));
\r
1341 dinvgamma = 1/gammaG;
\r
1342 dMax = pow(255.0, dinvgamma) / 255.0;
\r
1343 BYTE cTableG[256];
\r
1344 for (i=0;i<256;i++) {
\r
1345 cTableG[i] = (BYTE)max(0,min(255,(int)( pow((double)i, dinvgamma) / dMax)));
\r
1348 dinvgamma = 1/gammaB;
\r
1349 dMax = pow(255.0, dinvgamma) / 255.0;
\r
1350 BYTE cTableB[256];
\r
1351 for (i=0;i<256;i++) {
\r
1352 cTableB[i] = (BYTE)max(0,min(255,(int)( pow((double)i, dinvgamma) / dMax)));
\r
1355 return Lut(cTableR, cTableG, cTableB);
\r
1357 ////////////////////////////////////////////////////////////////////////////////
\r
1359 //#if !defined (_WIN32_WCE)
\r
1361 * Adjusts the intensity of each pixel to the median intensity of its surrounding pixels.
\r
1362 * \param Ksize: size of the kernel.
\r
1363 * \return true if everything is ok
\r
1365 bool CxImage::Median(long Ksize)
\r
1367 if (!pDib) return false;
\r
1369 long k2 = Ksize/2;
\r
1370 long kmax= Ksize-k2;
\r
1373 RGBQUAD* kernel = (RGBQUAD*)malloc(Ksize*Ksize*sizeof(RGBQUAD));
\r
1375 CxImage tmp(*this);
\r
1376 if (!tmp.IsValid()){
\r
1377 strcpy(info.szLastError,tmp.GetLastError());
\r
1381 long xmin,xmax,ymin,ymax;
\r
1383 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
1384 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
1387 xmax = head.biWidth; ymax=head.biHeight;
\r
1390 for(long y=ymin; y<ymax; y++){
\r
1391 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
1392 if (info.nEscape) break;
\r
1393 for(long x=xmin; x<xmax; x++){
\r
1394 #if CXIMAGE_SUPPORT_SELECTION
\r
1395 if (BlindSelectionIsInside(x,y))
\r
1396 #endif //CXIMAGE_SUPPORT_SELECTION
\r
1398 for(j=-k2, i=0;j<kmax;j++)
\r
1399 for(k=-k2;k<kmax;k++)
\r
1400 if (IsInside(x+j,y+k))
\r
1401 kernel[i++]=BlindGetPixelColor(x+j,y+k);
\r
1403 qsort(kernel, i, sizeof(RGBQUAD), CompareColors);
\r
1404 tmp.SetPixelColor(x,y,kernel[i/2]);
\r
1412 //#endif //_WIN32_WCE
\r
1413 ////////////////////////////////////////////////////////////////////////////////
\r
1415 * Adds an uniform noise to the image
\r
1416 * \param level: can be from 0 (no noise) to 255 (lot of noise).
\r
1417 * \return true if everything is ok
\r
1419 bool CxImage::Noise(long level)
\r
1421 if (!pDib) return false;
\r
1424 long xmin,xmax,ymin,ymax,n;
\r
1426 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
1427 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
1430 xmax = head.biWidth; ymax=head.biHeight;
\r
1433 for(long y=ymin; y<ymax; y++){
\r
1434 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin)); //<zhanghk><Anatoly Ivasyuk>
\r
1435 for(long x=xmin; x<xmax; x++){
\r
1436 #if CXIMAGE_SUPPORT_SELECTION
\r
1437 if (BlindSelectionIsInside(x,y))
\r
1438 #endif //CXIMAGE_SUPPORT_SELECTION
\r
1440 color = BlindGetPixelColor(x,y);
\r
1441 n=(long)((rand()/(float)RAND_MAX - 0.5)*level);
\r
1442 color.rgbRed = (BYTE)max(0,min(255,(int)(color.rgbRed + n)));
\r
1443 n=(long)((rand()/(float)RAND_MAX - 0.5)*level);
\r
1444 color.rgbGreen = (BYTE)max(0,min(255,(int)(color.rgbGreen + n)));
\r
1445 n=(long)((rand()/(float)RAND_MAX - 0.5)*level);
\r
1446 color.rgbBlue = (BYTE)max(0,min(255,(int)(color.rgbBlue + n)));
\r
1447 BlindSetPixelColor(x,y,color);
\r
1453 ////////////////////////////////////////////////////////////////////////////////
\r
1455 * Computes the bidimensional FFT or DFT of the image.
\r
1456 * - The images are processed as grayscale
\r
1457 * - If the dimensions of the image are a power of, 2 the FFT is performed automatically.
\r
1458 * - If dstReal and/or dstImag are NULL, the resulting images replaces the original(s).
\r
1459 * - Note: with 8 bits there is a HUGE loss in the dynamics. The function tries
\r
1460 * to keep an acceptable SNR, but 8bit = 48dB...
\r
1462 * \param srcReal, srcImag: source images: One can be NULL, but not both
\r
1463 * \param dstReal, dstImag: destination images. Can be NULL.
\r
1464 * \param direction: 1 = forward, -1 = inverse.
\r
1465 * \param bForceFFT: if true, the images are resampled to make the dimensions a power of 2.
\r
1466 * \param bMagnitude: if true, the real part returns the magnitude, the imaginary part returns the phase
\r
1467 * \return true if everything is ok
\r
1469 bool CxImage::FFT2(CxImage* srcReal, CxImage* srcImag, CxImage* dstReal, CxImage* dstImag,
\r
1470 long direction, bool bForceFFT, bool bMagnitude)
\r
1472 //check if there is something to convert
\r
1473 if (srcReal==NULL && srcImag==NULL) return false;
\r
1476 //get width and height
\r
1478 w=srcReal->GetWidth();
\r
1479 h=srcReal->GetHeight();
\r
1481 w=srcImag->GetWidth();
\r
1482 h=srcImag->GetHeight();
\r
1485 bool bXpow2 = IsPowerof2(w);
\r
1486 bool bYpow2 = IsPowerof2(h);
\r
1487 //if bForceFFT, width AND height must be powers of 2
\r
1488 if (bForceFFT && !(bXpow2 && bYpow2)) {
\r
1492 while((1<<i)<w) i++;
\r
1497 while((1<<i)<h) i++;
\r
1502 // I/O images for FFT
\r
1503 CxImage *tmpReal,*tmpImag;
\r
1506 tmpReal = (dstReal) ? dstReal : srcReal;
\r
1507 tmpImag = (dstImag) ? dstImag : srcImag;
\r
1509 // src!=dst -> copy the image
\r
1510 if (srcReal && dstReal) tmpReal->Copy(*srcReal,true,false,false);
\r
1511 if (srcImag && dstImag) tmpImag->Copy(*srcImag,true,false,false);
\r
1513 // dst&&src are empty -> create new one, else turn to GrayScale
\r
1514 if (srcReal==0 && dstReal==0){
\r
1515 tmpReal = new CxImage(w,h,8);
\r
1516 tmpReal->Clear(0);
\r
1517 tmpReal->SetGrayPalette();
\r
1519 if (!tmpReal->IsGrayScale()) tmpReal->GrayScale();
\r
1521 if (srcImag==0 && dstImag==0){
\r
1522 tmpImag = new CxImage(w,h,8);
\r
1523 tmpImag->Clear(0);
\r
1524 tmpImag->SetGrayPalette();
\r
1526 if (!tmpImag->IsGrayScale()) tmpImag->GrayScale();
\r
1529 if (!(tmpReal->IsValid() && tmpImag->IsValid())){
\r
1530 if (srcReal==0 && dstReal==0) delete tmpReal;
\r
1531 if (srcImag==0 && dstImag==0) delete tmpImag;
\r
1535 //resample for FFT, if necessary
\r
1536 tmpReal->Resample(w,h,0);
\r
1537 tmpImag->Resample(w,h,0);
\r
1539 //ok, here we have 2 (w x h), grayscale images ready for a FFT
\r
1546 //double mean = tmpReal->Mean();
\r
1547 /* Allocate memory for the grid */
\r
1548 grid = (_complex **)malloc(w * sizeof(_complex));
\r
1549 for (k=0;k<w;k++) {
\r
1550 grid[k] = (_complex *)malloc(h * sizeof(_complex));
\r
1552 for (j=0;j<h;j++) {
\r
1553 for (k=0;k<w;k++) {
\r
1554 grid[k][j].x = tmpReal->GetPixelIndex(k,j)-128;
\r
1555 grid[k][j].y = tmpImag->GetPixelIndex(k,j)-128;
\r
1560 double *real2,*imag2;
\r
1561 real2 = (double*)malloc(max(w,h) * sizeof(double));
\r
1562 imag2 = (double*)malloc(max(w,h) * sizeof(double));
\r
1564 /* Transform the rows */
\r
1565 real = (double *)malloc(w * sizeof(double));
\r
1566 imag = (double *)malloc(w * sizeof(double));
\r
1569 while((1<<m)<w) m++;
\r
1571 for (j=0;j<h;j++) {
\r
1572 for (k=0;k<w;k++) {
\r
1573 real[k] = grid[k][j].x;
\r
1574 imag[k] = grid[k][j].y;
\r
1577 if (bXpow2) FFT(direction,m,real,imag);
\r
1578 else DFT(direction,w,real,imag,real2,imag2);
\r
1580 for (k=0;k<w;k++) {
\r
1581 grid[k][j].x = real[k];
\r
1582 grid[k][j].y = imag[k];
\r
1588 /* Transform the columns */
\r
1589 real = (double *)malloc(h * sizeof(double));
\r
1590 imag = (double *)malloc(h * sizeof(double));
\r
1593 while((1<<m)<h) m++;
\r
1595 for (k=0;k<w;k++) {
\r
1596 for (j=0;j<h;j++) {
\r
1597 real[j] = grid[k][j].x;
\r
1598 imag[j] = grid[k][j].y;
\r
1601 if (bYpow2) FFT(direction,m,real,imag);
\r
1602 else DFT(direction,h,real,imag,real2,imag2);
\r
1604 for (j=0;j<h;j++) {
\r
1605 grid[k][j].x = real[j];
\r
1606 grid[k][j].y = imag[j];
\r
1615 /* converting from double to byte, there is a HUGE loss in the dynamics
\r
1616 "nn" tries to keep an acceptable SNR, but 8bit=48dB: don't ask more */
\r
1617 double nn=pow((double)2,(double)log((double)max(w,h))/(double)log((double)2)-4);
\r
1618 //reversed gain for reversed transform
\r
1619 if (direction==-1) nn=1/nn;
\r
1620 //bMagnitude : just to see it on the screen
\r
1621 if (bMagnitude) nn*=4;
\r
1623 for (j=0;j<h;j++) {
\r
1624 for (k=0;k<w;k++) {
\r
1626 tmpReal->SetPixelIndex(k,j,(BYTE)max(0,min(255,(nn*(3+log(_cabs(grid[k][j])))))));
\r
1627 if (grid[k][j].x==0){
\r
1628 tmpImag->SetPixelIndex(k,j,(BYTE)max(0,min(255,(128+(atan(grid[k][j].y/0.0000000001)*nn)))));
\r
1630 tmpImag->SetPixelIndex(k,j,(BYTE)max(0,min(255,(128+(atan(grid[k][j].y/grid[k][j].x)*nn)))));
\r
1633 tmpReal->SetPixelIndex(k,j,(BYTE)max(0,min(255,(128 + grid[k][j].x*nn))));
\r
1634 tmpImag->SetPixelIndex(k,j,(BYTE)max(0,min(255,(128 + grid[k][j].y*nn))));
\r
1639 for (k=0;k<w;k++) free (grid[k]);
\r
1642 if (srcReal==0 && dstReal==0) delete tmpReal;
\r
1643 if (srcImag==0 && dstImag==0) delete tmpImag;
\r
1647 ////////////////////////////////////////////////////////////////////////////////
\r
1648 bool CxImage::IsPowerof2(long x)
\r
1651 while ((1<<i)<x) i++;
\r
1652 if (x==(1<<i)) return true;
\r
1655 ////////////////////////////////////////////////////////////////////////////////
\r
1657 This computes an in-place complex-to-complex FFT
\r
1658 x and y are the real and imaginary arrays of n=2^m points.
\r
1660 dir = 1 gives forward transform
\r
1661 dir = -1 gives reverse transform
\r
1662 Written by Paul Bourke, July 1998
\r
1663 FFT algorithm by Cooley and Tukey, 1965
\r
1665 bool CxImage::FFT(int dir,int m,double *x,double *y)
\r
1667 long nn,i,i1,j,k,i2,l,l1,l2;
\r
1668 double c1,c2,tx,ty,t1,t2,u1,u2,z;
\r
1670 /* Calculate the number of points */
\r
1673 /* Do the bit reversal */
\r
1676 for (i=0;i<nn-1;i++) {
\r
1693 /* Compute the FFT */
\r
1697 for (l=0;l<m;l++) {
\r
1702 for (j=0;j<l1;j++) {
\r
1703 for (i=j;i<nn;i+=l2) {
\r
1705 t1 = u1 * x[i1] - u2 * y[i1];
\r
1706 t2 = u1 * y[i1] + u2 * x[i1];
\r
1707 x[i1] = x[i] - t1;
\r
1708 y[i1] = y[i] - t2;
\r
1712 z = u1 * c1 - u2 * c2;
\r
1713 u2 = u1 * c2 + u2 * c1;
\r
1716 c2 = sqrt((1.0 - c1) / 2.0);
\r
1719 c1 = sqrt((1.0 + c1) / 2.0);
\r
1722 /* Scaling for forward transform */
\r
1724 for (i=0;i<nn;i++) {
\r
1725 x[i] /= (double)nn;
\r
1726 y[i] /= (double)nn;
\r
1732 ////////////////////////////////////////////////////////////////////////////////
\r
1734 Direct fourier transform o(n)=n^2
\r
1735 Written by Paul Bourke, July 1998
\r
1737 bool CxImage::DFT(int dir,long m,double *x1,double *y1,double *x2,double *y2)
\r
1741 double cosarg,sinarg;
\r
1743 for (i=0;i<m;i++) {
\r
1746 arg = - dir * 2.0 * PI * i / (double)m;
\r
1747 for (k=0;k<m;k++) {
\r
1748 cosarg = cos(k * arg);
\r
1749 sinarg = sin(k * arg);
\r
1750 x2[i] += (x1[k] * cosarg - y1[k] * sinarg);
\r
1751 y2[i] += (x1[k] * sinarg + y1[k] * cosarg);
\r
1755 /* Copy the data back */
\r
1757 for (i=0;i<m;i++) {
\r
1758 x1[i] = x2[i] / m;
\r
1759 y1[i] = y2[i] / m;
\r
1762 for (i=0;i<m;i++) {
\r
1770 ////////////////////////////////////////////////////////////////////////////////
\r
1772 * Combines different color components into a single image
\r
1773 * \param r,g,b: color channels
\r
1774 * \param a: alpha layer, can be NULL
\r
1775 * \param colorspace: 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ
\r
1776 * \return true if everything is ok
\r
1778 bool CxImage::Combine(CxImage* r,CxImage* g,CxImage* b,CxImage* a, long colorspace)
\r
1780 if (r==0 || g==0 || b==0) return false;
\r
1782 long w = r->GetWidth();
\r
1783 long h = r->GetHeight();
\r
1792 #if CXIMAGE_SUPPORT_ALPHA
\r
1794 #endif //CXIMAGE_SUPPORT_ALPHA
\r
1798 for (long y=0;y<h;y++){
\r
1799 info.nProgress = (long)(100*y/h); //<Anatoly Ivasyuk>
\r
1800 for (long x=0;x<w;x++){
\r
1801 c.rgbRed=r->GetPixelIndex(x,y);
\r
1802 c.rgbGreen=g->GetPixelIndex(x,y);
\r
1803 c.rgbBlue=b->GetPixelIndex(x,y);
\r
1804 switch (colorspace){
\r
1806 BlindSetPixelColor(x,y,HSLtoRGB(c));
\r
1809 BlindSetPixelColor(x,y,YUVtoRGB(c));
\r
1812 BlindSetPixelColor(x,y,YIQtoRGB(c));
\r
1815 BlindSetPixelColor(x,y,XYZtoRGB(c));
\r
1818 BlindSetPixelColor(x,y,c);
\r
1820 #if CXIMAGE_SUPPORT_ALPHA
\r
1821 if (a) AlphaSet(x,y,a->GetPixelIndex(x,y));
\r
1822 #endif //CXIMAGE_SUPPORT_ALPHA
\r
1828 ////////////////////////////////////////////////////////////////////////////////
\r
1830 * Smart blurring to remove small defects, dithering or artifacts.
\r
1831 * \param radius: normally between 0.01 and 0.5
\r
1832 * \param niterations: should be trimmed with radius, to avoid blurring should be (radius*niterations)<1
\r
1833 * \param colorspace: 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ
\r
1834 * \return true if everything is ok
\r
1836 bool CxImage::Repair(float radius, long niterations, long colorspace)
\r
1838 if (!IsValid()) return false;
\r
1840 long w = GetWidth();
\r
1841 long h = GetHeight();
\r
1849 switch (colorspace){
\r
1851 SplitHSL(&r,&g,&b);
\r
1854 SplitYUV(&r,&g,&b);
\r
1857 SplitYIQ(&r,&g,&b);
\r
1860 SplitXYZ(&r,&g,&b);
\r
1863 SplitRGB(&r,&g,&b);
\r
1866 for (int i=0; i<niterations; i++){
\r
1867 RepairChannel(&r,radius);
\r
1868 RepairChannel(&g,radius);
\r
1869 RepairChannel(&b,radius);
\r
1873 #if CXIMAGE_SUPPORT_ALPHA
\r
1874 if (AlphaIsValid()){
\r
1875 a = new CxImage();
\r
1880 Combine(&r,&g,&b,a,colorspace);
\r
1886 ////////////////////////////////////////////////////////////////////////////////
\r
1887 bool CxImage::RepairChannel(CxImage *ch, float radius)
\r
1889 if (ch==NULL) return false;
\r
1892 if (!tmp.IsValid()){
\r
1893 strcpy(info.szLastError,tmp.GetLastError());
\r
1897 long w = ch->GetWidth()-1;
\r
1898 long h = ch->GetHeight()-1;
\r
1900 double correction,ix,iy,ixx,ixy,iyy;
\r
1901 int x,y,xy0,xp1,xm1,yp1,ym1;
\r
1903 for(x=1; x<w; x++){
\r
1904 for(y=1; y<h; y++){
\r
1906 xy0 = ch->BlindGetPixelIndex(x,y);
\r
1907 xm1 = ch->BlindGetPixelIndex(x-1,y);
\r
1908 xp1 = ch->BlindGetPixelIndex(x+1,y);
\r
1909 ym1 = ch->BlindGetPixelIndex(x,y-1);
\r
1910 yp1 = ch->BlindGetPixelIndex(x,y+1);
\r
1912 ix= (xp1-xm1)/2.0;
\r
1913 iy= (yp1-ym1)/2.0;
\r
1914 ixx= xp1 - 2.0 * xy0 + xm1;
\r
1915 iyy= yp1 - 2.0 * xy0 + ym1;
\r
1916 ixy=(ch->BlindGetPixelIndex(x+1,y+1) + ch->BlindGetPixelIndex(x-1,y-1) -
\r
1917 ch->BlindGetPixelIndex(x-1,y+1) - ch->BlindGetPixelIndex(x+1,y-1))/4.0;
\r
1919 correction = ((1.0+iy*iy)*ixx - ix*iy*ixy + (1.0+ix*ix)*iyy)/(1.0+ix*ix+iy*iy);
\r
1921 tmp.BlindSetPixelIndex(x,y,(BYTE)min(255,max(0,(xy0 + radius * correction + 0.5))));
\r
1925 for (x=0;x<=w;x++){
\r
1926 for(y=0; y<=h; y+=h){
\r
1927 xy0 = ch->BlindGetPixelIndex(x,y);
\r
1928 xm1 = ch->GetPixelIndex(x-1,y);
\r
1929 xp1 = ch->GetPixelIndex(x+1,y);
\r
1930 ym1 = ch->GetPixelIndex(x,y-1);
\r
1931 yp1 = ch->GetPixelIndex(x,y+1);
\r
1933 ix= (xp1-xm1)/2.0;
\r
1934 iy= (yp1-ym1)/2.0;
\r
1935 ixx= xp1 - 2.0 * xy0 + xm1;
\r
1936 iyy= yp1 - 2.0 * xy0 + ym1;
\r
1937 ixy=(ch->GetPixelIndex(x+1,y+1) + ch->GetPixelIndex(x-1,y-1) -
\r
1938 ch->GetPixelIndex(x-1,y+1) - ch->GetPixelIndex(x+1,y-1))/4.0;
\r
1940 correction = ((1.0+iy*iy)*ixx - ix*iy*ixy + (1.0+ix*ix)*iyy)/(1.0+ix*ix+iy*iy);
\r
1942 tmp.BlindSetPixelIndex(x,y,(BYTE)min(255,max(0,(xy0 + radius * correction + 0.5))));
\r
1945 for (x=0;x<=w;x+=w){
\r
1946 for (y=0;y<=h;y++){
\r
1947 xy0 = ch->BlindGetPixelIndex(x,y);
\r
1948 xm1 = ch->GetPixelIndex(x-1,y);
\r
1949 xp1 = ch->GetPixelIndex(x+1,y);
\r
1950 ym1 = ch->GetPixelIndex(x,y-1);
\r
1951 yp1 = ch->GetPixelIndex(x,y+1);
\r
1953 ix= (xp1-xm1)/2.0;
\r
1954 iy= (yp1-ym1)/2.0;
\r
1955 ixx= xp1 - 2.0 * xy0 + xm1;
\r
1956 iyy= yp1 - 2.0 * xy0 + ym1;
\r
1957 ixy=(ch->GetPixelIndex(x+1,y+1) + ch->GetPixelIndex(x-1,y-1) -
\r
1958 ch->GetPixelIndex(x-1,y+1) - ch->GetPixelIndex(x+1,y-1))/4.0;
\r
1960 correction = ((1.0+iy*iy)*ixx - ix*iy*ixy + (1.0+ix*ix)*iyy)/(1.0+ix*ix+iy*iy);
\r
1962 tmp.BlindSetPixelIndex(x,y,(BYTE)min(255,max(0,(xy0 + radius * correction + 0.5))));
\r
1966 ch->Transfer(tmp);
\r
1969 ////////////////////////////////////////////////////////////////////////////////
\r
1971 * Enhance the variations between adjacent pixels.
\r
1972 * Similar results can be achieved using Filter(),
\r
1973 * but the algorithms are different both in Edge() and in Contour().
\r
1974 * \return true if everything is ok
\r
1976 bool CxImage::Contour()
\r
1978 if (!pDib) return false;
\r
1981 long k2 = Ksize/2;
\r
1982 long kmax= Ksize-k2;
\r
1984 BYTE maxr,maxg,maxb;
\r
1985 RGBQUAD pix1,pix2;
\r
1987 CxImage tmp(*this);
\r
1988 if (!tmp.IsValid()){
\r
1989 strcpy(info.szLastError,tmp.GetLastError());
\r
1993 long xmin,xmax,ymin,ymax;
\r
1995 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
1996 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
1999 xmax = head.biWidth; ymax=head.biHeight;
\r
2002 for(long y=ymin; y<ymax; y++){
\r
2003 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
2004 if (info.nEscape) break;
\r
2005 for(long x=xmin; x<xmax; x++){
\r
2006 #if CXIMAGE_SUPPORT_SELECTION
\r
2007 if (BlindSelectionIsInside(x,y))
\r
2008 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2010 pix1 = BlindGetPixelColor(x,y);
\r
2012 for(j=-k2, i=0;j<kmax;j++){
\r
2013 for(k=-k2;k<kmax;k++, i++){
\r
2014 if (!IsInside(x+j,y+k)) continue;
\r
2015 pix2 = BlindGetPixelColor(x+j,y+k);
\r
2016 if ((pix2.rgbBlue-pix1.rgbBlue)>maxb) maxb = pix2.rgbBlue;
\r
2017 if ((pix2.rgbGreen-pix1.rgbGreen)>maxg) maxg = pix2.rgbGreen;
\r
2018 if ((pix2.rgbRed-pix1.rgbRed)>maxr) maxr = pix2.rgbRed;
\r
2021 pix1.rgbBlue=(BYTE)(255-maxb);
\r
2022 pix1.rgbGreen=(BYTE)(255-maxg);
\r
2023 pix1.rgbRed=(BYTE)(255-maxr);
\r
2024 tmp.BlindSetPixelColor(x,y,pix1);
\r
2031 ////////////////////////////////////////////////////////////////////////////////
\r
2033 * Adds a random offset to each pixel in the image
\r
2034 * \param radius: maximum pixel displacement
\r
2035 * \return true if everything is ok
\r
2037 bool CxImage::Jitter(long radius)
\r
2039 if (!pDib) return false;
\r
2043 CxImage tmp(*this);
\r
2044 if (!tmp.IsValid()){
\r
2045 strcpy(info.szLastError,tmp.GetLastError());
\r
2049 long xmin,xmax,ymin,ymax;
\r
2051 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2052 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2055 xmax = head.biWidth; ymax=head.biHeight;
\r
2058 for(long y=ymin; y<ymax; y++){
\r
2059 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
2060 if (info.nEscape) break;
\r
2061 for(long x=xmin; x<xmax; x++){
\r
2062 #if CXIMAGE_SUPPORT_SELECTION
\r
2063 if (BlindSelectionIsInside(x,y))
\r
2064 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2066 nx=x+(long)((rand()/(float)RAND_MAX - 0.5)*(radius*2));
\r
2067 ny=y+(long)((rand()/(float)RAND_MAX - 0.5)*(radius*2));
\r
2068 if (!IsInside(nx,ny)) {
\r
2072 if (head.biClrUsed==0){
\r
2073 tmp.BlindSetPixelColor(x,y,BlindGetPixelColor(nx,ny));
\r
2075 tmp.BlindSetPixelIndex(x,y,BlindGetPixelIndex(nx,ny));
\r
2077 #if CXIMAGE_SUPPORT_ALPHA
\r
2078 tmp.AlphaSet(x,y,AlphaGet(nx,ny));
\r
2079 #endif //CXIMAGE_SUPPORT_ALPHA
\r
2086 ////////////////////////////////////////////////////////////////////////////////
\r
2088 * generates a 1-D convolution matrix to be used for each pass of
\r
2089 * a two-pass gaussian blur. Returns the length of the matrix.
\r
2090 * \author [nipper]
\r
2092 int CxImage::gen_convolve_matrix (float radius, float **cmatrix_p)
\r
2094 int matrix_length;
\r
2095 int matrix_midpoint;
\r
2101 /* we want to generate a matrix that goes out a certain radius
\r
2102 * from the center, so we have to go out ceil(rad-0.5) pixels,
\r
2103 * inlcuding the center pixel. Of course, that's only in one direction,
\r
2104 * so we have to go the same amount in the other direction, but not count
\r
2105 * the center pixel again. So we double the previous result and subtract
\r
2107 * The radius parameter that is passed to this function is used as
\r
2108 * the standard deviation, and the radius of effect is the
\r
2109 * standard deviation * 2. It's a little confusing.
\r
2110 * <DP> modified scaling, so that matrix_lenght = 1+2*radius parameter
\r
2112 radius = (float)fabs(0.5*radius) + 0.25f;
\r
2115 radius = std_dev * 2;
\r
2117 /* go out 'radius' in each direction */
\r
2118 matrix_length = int (2 * ceil(radius-0.5) + 1);
\r
2119 if (matrix_length <= 0) matrix_length = 1;
\r
2120 matrix_midpoint = matrix_length/2 + 1;
\r
2121 *cmatrix_p = new float[matrix_length];
\r
2122 cmatrix = *cmatrix_p;
\r
2124 /* Now we fill the matrix by doing a numeric integration approximation
\r
2125 * from -2*std_dev to 2*std_dev, sampling 50 points per pixel.
\r
2126 * We do the bottom half, mirror it to the top half, then compute the
\r
2127 * center point. Otherwise asymmetric quantization errors will occur.
\r
2128 * The formula to integrate is e^-(x^2/2s^2).
\r
2131 /* first we do the top (right) half of matrix */
\r
2132 for (i = matrix_length/2 + 1; i < matrix_length; i++)
\r
2134 float base_x = i - (float)floor((float)(matrix_length/2)) - 0.5f;
\r
2136 for (j = 1; j <= 50; j++)
\r
2138 if ( base_x+0.02*j <= radius )
\r
2139 sum += (float)exp (-(base_x+0.02*j)*(base_x+0.02*j) /
\r
2140 (2*std_dev*std_dev));
\r
2142 cmatrix[i] = sum/50;
\r
2145 /* mirror the thing to the bottom half */
\r
2146 for (i=0; i<=matrix_length/2; i++) {
\r
2147 cmatrix[i] = cmatrix[matrix_length-1-i];
\r
2150 /* find center val -- calculate an odd number of quanta to make it symmetric,
\r
2151 * even if the center point is weighted slightly higher than others. */
\r
2153 for (j=0; j<=50; j++)
\r
2155 sum += (float)exp (-(0.5+0.02*j)*(0.5+0.02*j) /
\r
2156 (2*std_dev*std_dev));
\r
2158 cmatrix[matrix_length/2] = sum/51;
\r
2160 /* normalize the distribution by scaling the total sum to one */
\r
2162 for (i=0; i<matrix_length; i++) sum += cmatrix[i];
\r
2163 for (i=0; i<matrix_length; i++) cmatrix[i] = cmatrix[i] / sum;
\r
2165 return matrix_length;
\r
2167 ////////////////////////////////////////////////////////////////////////////////
\r
2169 * generates a lookup table for every possible product of 0-255 and
\r
2170 * each value in the convolution matrix. The returned array is
\r
2171 * indexed first by matrix position, then by input multiplicand (?)
\r
2173 * \author [nipper]
\r
2175 float* CxImage::gen_lookup_table (float *cmatrix, int cmatrix_length)
\r
2177 float* lookup_table = new float[cmatrix_length * 256];
\r
2178 float* lookup_table_p = lookup_table;
\r
2179 float* cmatrix_p = cmatrix;
\r
2181 for (int i=0; i<cmatrix_length; i++)
\r
2183 for (int j=0; j<256; j++)
\r
2185 *(lookup_table_p++) = *cmatrix_p * (float)j;
\r
2190 return lookup_table;
\r
2192 ////////////////////////////////////////////////////////////////////////////////
\r
2194 * this function is written as if it is blurring a column at a time,
\r
2195 * even though it can operate on rows, too. There is no difference
\r
2196 * in the processing of the lines, at least to the blur_line function.
\r
2197 * \author [nipper]
\r
2199 void CxImage::blur_line (float *ctable, float *cmatrix, int cmatrix_length, BYTE* cur_col, BYTE* dest_col, int y, long bytes)
\r
2205 int cmatrix_middle = cmatrix_length/2;
\r
2213 /* this first block is the same as the non-optimized version --
\r
2214 * it is only used for very small pictures, so speed isn't a
\r
2217 if (cmatrix_length > y)
\r
2219 for (row = 0; row < y ; row++)
\r
2222 /* find the scale factor */
\r
2223 for (j = 0; j < y ; j++)
\r
2225 /* if the index is in bounds, add it to the scale counter */
\r
2226 if ((j + cmatrix_middle - row >= 0) &&
\r
2227 (j + cmatrix_middle - row < cmatrix_length))
\r
2228 scale += cmatrix[j + cmatrix_middle - row];
\r
2230 for (i = 0; i<bytes; i++)
\r
2233 for (j = 0; j < y; j++)
\r
2235 if ((j >= row - cmatrix_middle) &&
\r
2236 (j <= row + cmatrix_middle))
\r
2237 sum += cur_col[j*bytes + i] * cmatrix[j];
\r
2239 dest_col[row*bytes + i] = (BYTE)(0.5f + sum / scale);
\r
2245 /* for the edge condition, we only use available info and scale to one */
\r
2246 for (row = 0; row < cmatrix_middle; row++)
\r
2248 /* find scale factor */
\r
2250 for (j = cmatrix_middle - row; j<cmatrix_length; j++)
\r
2251 scale += cmatrix[j];
\r
2252 for (i = 0; i<bytes; i++)
\r
2255 for (j = cmatrix_middle - row; j<cmatrix_length; j++)
\r
2257 sum += cur_col[(row + j-cmatrix_middle)*bytes + i] * cmatrix[j];
\r
2259 dest_col[row*bytes + i] = (BYTE)(0.5f + sum / scale);
\r
2262 /* go through each pixel in each col */
\r
2263 dest_col_p = dest_col + row*bytes;
\r
2264 for (; row < y-cmatrix_middle; row++)
\r
2266 cur_col_p = (row - cmatrix_middle) * bytes + cur_col;
\r
2267 for (i = 0; i<bytes; i++)
\r
2270 cmatrix_p = cmatrix;
\r
2271 cur_col_p1 = cur_col_p;
\r
2272 ctable_p = ctable;
\r
2273 for (j = cmatrix_length; j>0; j--)
\r
2275 sum += *(ctable_p + *cur_col_p1);
\r
2276 cur_col_p1 += bytes;
\r
2280 *(dest_col_p++) = (BYTE)(0.5f + sum);
\r
2284 /* for the edge condition , we only use available info, and scale to one */
\r
2285 for (; row < y; row++)
\r
2287 /* find scale factor */
\r
2289 for (j = 0; j< y-row + cmatrix_middle; j++)
\r
2290 scale += cmatrix[j];
\r
2291 for (i = 0; i<bytes; i++)
\r
2294 for (j = 0; j<y-row + cmatrix_middle; j++)
\r
2296 sum += cur_col[(row + j-cmatrix_middle)*bytes + i] * cmatrix[j];
\r
2298 dest_col[row*bytes + i] = (BYTE) (0.5f + sum / scale);
\r
2303 ////////////////////////////////////////////////////////////////////////////////
\r
2307 void CxImage::blur_text (BYTE threshold, BYTE decay, BYTE max_depth, CxImage* iSrc, CxImage* iDst, BYTE bytes)
\r
2310 BYTE *pSrc, *pSrc2, *pSrc3, *pDst;
\r
2314 if (max_depth<1) max_depth = 1;
\r
2316 long nmin,nmax,xmin,xmax,ymin,ymax;
\r
2318 xmax = iSrc->head.biWidth;
\r
2319 ymax = iSrc->head.biHeight;
\r
2321 if (xmin==xmax || ymin==ymax) return;
\r
2323 nmin = xmin * bytes;
\r
2324 nmax = xmax * bytes;
\r
2326 CImageIterator itSrc(iSrc);
\r
2327 CImageIterator itTmp(iDst);
\r
2329 double dbScaler = 100.0f/(ymax-ymin)/bytes;
\r
2331 for (n=0; n<bytes; n++){
\r
2332 for (y=ymin+1;y<(ymax-1);y++)
\r
2334 if (info.nEscape) break;
\r
2335 info.nProgress = (long)((y-ymin)*dbScaler*(1+n));
\r
2337 pSrc = itSrc.GetRow(y);
\r
2338 pSrc2 = itSrc.GetRow(y+1);
\r
2339 pSrc3 = itSrc.GetRow(y-1);
\r
2340 pDst = itTmp.GetRow(y);
\r
2342 //scan left to right
\r
2343 for (x=n+nmin /*,i=xmin*/; x<(nmax-1); x+=bytes /*,i++*/)
\r
2346 pivot = pSrc[z]-threshold;
\r
2347 //find upper corner
\r
2348 if (pSrc[x]<pivot && pSrc2[z]<pivot && pSrc3[x]>=pivot){
\r
2349 while (z<nmax && pSrc2[z]<pSrc[x+bytes] && pSrc[x+bytes]<=pSrc[z]){
\r
2353 m = (decay>1) ? ((m/bytes)/decay+1) : m/bytes;
\r
2354 if (m>max_depth) m = max_depth;
\r
2355 step = (BYTE)((pSrc[x+bytes]-pSrc[x])/(m+1));
\r
2357 pDst[x+m*bytes] = (BYTE)(pDst[x]+(step*(m+1)));
\r
2360 //find lower corner
\r
2362 if (pSrc[x]<pivot && pSrc3[z]<pivot && pSrc2[x]>=pivot){
\r
2363 while (z<nmax && pSrc3[z]<pSrc[x+bytes] && pSrc[x+bytes]<=pSrc[z]){
\r
2367 m = (decay>1) ? ((m/bytes)/decay+1) : m/bytes;
\r
2368 if (m>max_depth) m = max_depth;
\r
2369 step = (BYTE)((pSrc[x+bytes]-pSrc[x])/(m+1));
\r
2371 pDst[x+m*bytes] = (BYTE)(pDst[x]+(step*(m+1)));
\r
2375 //scan right to left
\r
2376 for (x=nmax-1-n /*,i=(xmax-1)*/; x>0; x-=bytes /*,i--*/)
\r
2379 pivot = pSrc[z]-threshold;
\r
2380 //find upper corner
\r
2381 if (pSrc[x]<pivot && pSrc2[z]<pivot && pSrc3[x]>=pivot){
\r
2382 while (z>n && pSrc2[z]<pSrc[x-bytes] && pSrc[x-bytes]<=pSrc[z]){
\r
2386 m = (decay>1) ? ((m/bytes)/decay+1) : m/bytes;
\r
2387 if (m>max_depth) m = max_depth;
\r
2388 step = (BYTE)((pSrc[x-bytes]-pSrc[x])/(m+1));
\r
2390 pDst[x-m*bytes] = (BYTE)(pDst[x]+(step*(m+1)));
\r
2393 //find lower corner
\r
2395 if (pSrc[x]<pivot && pSrc3[z]<pivot && pSrc2[x]>=pivot){
\r
2396 while (z>n && pSrc3[z]<pSrc[x-bytes] && pSrc[x-bytes]<=pSrc[z]){
\r
2400 m = (decay>1) ? ((m/bytes)/decay+1) : m/bytes;
\r
2401 if (m>max_depth) m = max_depth;
\r
2402 step = (BYTE)((pSrc[x-bytes]-pSrc[x])/(m+1));
\r
2404 pDst[x-m*bytes] = (BYTE)(pDst[x]+(step*(m+1)));
\r
2411 ////////////////////////////////////////////////////////////////////////////////
\r
2415 bool CxImage::TextBlur(BYTE threshold, BYTE decay, BYTE max_depth, bool bBlurHorizontal, bool bBlurVertical, CxImage* iDst)
\r
2417 if (!pDib) return false;
\r
2419 RGBQUAD* pPalette=NULL;
\r
2420 WORD bpp = GetBpp();
\r
2422 //the routine is optimized for RGB or GrayScale images
\r
2423 if (!(head.biBitCount == 24 || IsGrayScale())){
\r
2424 pPalette = new RGBQUAD[head.biClrUsed];
\r
2425 memcpy(pPalette, GetPalette(),GetPaletteSize());
\r
2426 if (!IncreaseBpp(24))
\r
2430 CxImage tmp(*this);
\r
2431 if (!tmp.IsValid()){
\r
2432 strcpy(info.szLastError,tmp.GetLastError());
\r
2436 if (bBlurHorizontal)
\r
2437 blur_text(threshold, decay, max_depth, this, &tmp, head.biBitCount>>3);
\r
2439 if (bBlurVertical){
\r
2440 CxImage src2(*this);
\r
2441 src2.RotateLeft();
\r
2443 blur_text(threshold, decay, max_depth, &src2, &tmp, head.biBitCount>>3);
\r
2444 tmp.RotateRight();
\r
2447 #if CXIMAGE_SUPPORT_SELECTION
\r
2448 //restore the non selected region
\r
2450 for(long y=0; y<head.biHeight; y++){
\r
2451 for(long x=0; x<head.biWidth; x++){
\r
2452 if (!BlindSelectionIsInside(x,y)){
\r
2453 tmp.BlindSetPixelColor(x,y,BlindGetPixelColor(x,y));
\r
2458 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2460 //if necessary, restore the original BPP and palette
\r
2462 tmp.DecreaseBpp(bpp, true, pPalette);
\r
2463 delete [] pPalette;
\r
2466 if (iDst) iDst->Transfer(tmp);
\r
2467 else Transfer(tmp);
\r
2471 ////////////////////////////////////////////////////////////////////////////////
\r
2473 * \author [nipper]; changes [DP]
\r
2475 bool CxImage::GaussianBlur(float radius /*= 1.0f*/, CxImage* iDst /*= 0*/)
\r
2477 if (!pDib) return false;
\r
2479 RGBQUAD* pPalette=NULL;
\r
2480 WORD bpp = GetBpp();
\r
2482 //the routine is optimized for RGB or GrayScale images
\r
2483 if (!(head.biBitCount == 24 || IsGrayScale())){
\r
2484 pPalette = new RGBQUAD[head.biClrUsed];
\r
2485 memcpy(pPalette, GetPalette(),GetPaletteSize());
\r
2486 if (!IncreaseBpp(24))
\r
2490 CxImage tmp_x(*this, false, true, true);
\r
2491 if (!tmp_x.IsValid()){
\r
2492 strcpy(info.szLastError,tmp_x.GetLastError());
\r
2496 // generate convolution matrix and make sure it's smaller than each dimension
\r
2497 float *cmatrix = NULL;
\r
2498 int cmatrix_length = gen_convolve_matrix(radius, &cmatrix);
\r
2499 // generate lookup table
\r
2500 float *ctable = gen_lookup_table(cmatrix, cmatrix_length);
\r
2503 int bypp = head.biBitCount>>3;
\r
2505 CImageIterator itSrc(this);
\r
2506 CImageIterator itTmp(&tmp_x);
\r
2508 double dbScaler = 50.0f/head.biHeight;
\r
2511 for (y=0;y<head.biHeight;y++)
\r
2513 if (info.nEscape) break;
\r
2514 info.nProgress = (long)(y*dbScaler);
\r
2516 blur_line(ctable, cmatrix, cmatrix_length, itSrc.GetRow(y), itTmp.GetRow(y), head.biWidth, bypp);
\r
2519 CxImage tmp_y(tmp_x, false, true, true);
\r
2520 if (!tmp_y.IsValid()){
\r
2521 strcpy(info.szLastError,tmp_y.GetLastError());
\r
2525 CImageIterator itDst(&tmp_y);
\r
2528 BYTE* cur_col = (BYTE*)malloc(bypp*head.biHeight);
\r
2529 BYTE* dest_col = (BYTE*)malloc(bypp*head.biHeight);
\r
2531 dbScaler = 50.0f/head.biWidth;
\r
2533 for (x=0;x<head.biWidth;x++)
\r
2535 if (info.nEscape) break;
\r
2536 info.nProgress = (long)(50.0f+x*dbScaler);
\r
2538 itTmp.GetCol(cur_col, x);
\r
2539 itDst.GetCol(dest_col, x);
\r
2540 blur_line(ctable, cmatrix, cmatrix_length, cur_col, dest_col, head.biHeight, bypp);
\r
2541 itDst.SetCol(dest_col, x);
\r
2547 delete [] cmatrix;
\r
2550 #if CXIMAGE_SUPPORT_SELECTION
\r
2551 //restore the non selected region
\r
2553 for(y=0; y<head.biHeight; y++){
\r
2554 for(x=0; x<head.biWidth; x++){
\r
2555 if (!BlindSelectionIsInside(x,y)){
\r
2556 tmp_y.BlindSetPixelColor(x,y,BlindGetPixelColor(x,y));
\r
2561 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2563 //if necessary, restore the original BPP and palette
\r
2565 tmp_y.DecreaseBpp(bpp, false, pPalette);
\r
2566 if (iDst) DecreaseBpp(bpp, false, pPalette);
\r
2567 delete [] pPalette;
\r
2570 if (iDst) iDst->Transfer(tmp_y);
\r
2571 else Transfer(tmp_y);
\r
2575 ////////////////////////////////////////////////////////////////////////////////
\r
2577 * \author [DP],[nipper]
\r
2579 bool CxImage::SelectiveBlur(float radius, BYTE threshold, CxImage* iDst)
\r
2581 if (!pDib) return false;
\r
2583 RGBQUAD* pPalette=NULL;
\r
2584 WORD bpp = GetBpp();
\r
2586 CxImage Tmp(*this, true, true, true);
\r
2587 if (!Tmp.IsValid()){
\r
2588 strcpy(info.szLastError,Tmp.GetLastError());
\r
2592 //the routine is optimized for RGB or GrayScale images
\r
2593 if (!(head.biBitCount == 24 || IsGrayScale())){
\r
2594 pPalette = new RGBQUAD[head.biClrUsed];
\r
2595 memcpy(pPalette, GetPalette(),GetPaletteSize());
\r
2596 if (!Tmp.IncreaseBpp(24))
\r
2600 CxImage Dst(Tmp, true, true, true);
\r
2601 if (!Dst.IsValid()){
\r
2602 strcpy(info.szLastError,Dst.GetLastError());
\r
2606 //build the difference mask
\r
2607 BYTE thresh_dw = (BYTE)max( 0 ,(int)(128 - threshold));
\r
2608 BYTE thresh_up = (BYTE)min(255,(int)(128 + threshold));
\r
2609 long kernel[]={-100,-100,-100,-100,801,-100,-100,-100,-100};
\r
2610 if (!Tmp.Filter(kernel,3,800,128)){
\r
2611 strcpy(info.szLastError,Tmp.GetLastError());
\r
2615 //if the image has no selection, build a selection for the whole image
\r
2616 if (!Tmp.SelectionIsValid()){
\r
2617 Tmp.SelectionCreate();
\r
2618 Tmp.SelectionClear(255);
\r
2621 long xmin,xmax,ymin,ymax;
\r
2622 xmin = Tmp.info.rSelectionBox.left;
\r
2623 xmax = Tmp.info.rSelectionBox.right;
\r
2624 ymin = Tmp.info.rSelectionBox.bottom;
\r
2625 ymax = Tmp.info.rSelectionBox.top;
\r
2627 //modify the selection where the difference mask is over the threshold
\r
2628 for(long y=ymin; y<ymax; y++){
\r
2629 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
2630 if (info.nEscape) break;
\r
2631 for(long x=xmin; x<xmax; x++){
\r
2632 if(Tmp.BlindSelectionIsInside(x,y)){
\r
2633 RGBQUAD c = Tmp.BlindGetPixelColor(x,y);
\r
2634 if ((c.rgbRed < thresh_dw || c.rgbRed > thresh_up) ||
\r
2635 (c.rgbGreen < thresh_dw || c.rgbGreen > thresh_up) ||
\r
2636 (c.rgbBlue < thresh_dw || c.rgbBlue > thresh_up))
\r
2638 Tmp.SelectionSet(x,y,0);
\r
2644 //blur the image (only in the selected pixels)
\r
2645 Dst.SelectionCopy(Tmp);
\r
2646 if (!Dst.GaussianBlur(radius)){
\r
2647 strcpy(info.szLastError,Dst.GetLastError());
\r
2651 //restore the original selection
\r
2652 Dst.SelectionCopy(*this);
\r
2654 //if necessary, restore the original BPP and palette
\r
2656 Dst.DecreaseBpp(bpp, false, pPalette);
\r
2657 delete [] pPalette;
\r
2660 if (iDst) iDst->Transfer(Dst);
\r
2661 else Transfer(Dst);
\r
2665 ////////////////////////////////////////////////////////////////////////////////
\r
2667 * sharpen the image by subtracting a blurred copy from the original image.
\r
2668 * \param radius: width in pixels of the blurring effect. Range: >0; default = 5.
\r
2669 * \param amount: strength of the filter. Range: 0.0 (none) to 1.0 (max); default = 0.5
\r
2670 * \param threshold: difference, between blurred and original pixel, to trigger the filter
\r
2671 * Range: 0 (always triggered) to 255 (never triggered); default = 0.
\r
2672 * \return true if everything is ok
\r
2673 * \author [nipper]; changes [DP]
\r
2675 bool CxImage::UnsharpMask(float radius /*= 5.0*/, float amount /*= 0.5*/, int threshold /*= 0*/)
\r
2677 if (!pDib) return false;
\r
2679 RGBQUAD* pPalette=NULL;
\r
2680 WORD bpp = GetBpp();
\r
2682 //the routine is optimized for RGB or GrayScale images
\r
2683 if (!(head.biBitCount == 24 || IsGrayScale())){
\r
2684 pPalette = new RGBQUAD[head.biClrUsed];
\r
2685 memcpy(pPalette, GetPalette(),GetPaletteSize());
\r
2686 if (!IncreaseBpp(24))
\r
2691 if (!GaussianBlur(radius,&iDst))
\r
2694 CImageIterator itSrc(this);
\r
2695 CImageIterator itDst(&iDst);
\r
2697 long xmin,xmax,ymin,ymax;
\r
2699 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2700 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2703 xmax = head.biWidth; ymax=head.biHeight;
\r
2706 if (xmin==xmax || ymin==ymax)
\r
2709 double dbScaler = 100.0/(ymax-ymin);
\r
2710 int bypp = head.biBitCount>>3;
\r
2712 // merge the source and destination (which currently contains
\r
2713 // the blurred version) images
\r
2714 for (long y=ymin; y<ymax; y++)
\r
2716 if (info.nEscape) break;
\r
2717 info.nProgress = (long)((y-ymin)*dbScaler);
\r
2720 BYTE* cur_row = itSrc.GetRow(y);
\r
2722 BYTE* dest_row = itDst.GetRow(y);
\r
2723 // combine the two
\r
2724 for (long x=xmin; x<xmax; x++) {
\r
2725 #if CXIMAGE_SUPPORT_SELECTION
\r
2726 if (BlindSelectionIsInside(x,y))
\r
2727 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2729 for (long b=0, z=x*bypp; b<bypp; b++, z++){
\r
2730 int diff = cur_row[z] - dest_row[z];
\r
2733 if (abs(diff) < threshold){
\r
2734 dest_row[z] = cur_row[z];
\r
2736 dest_row[z] = (BYTE)min(255, max(0,(int)(cur_row[z] + amount * diff)));
\r
2743 //if necessary, restore the original BPP and palette
\r
2745 iDst.DecreaseBpp(bpp, false, pPalette);
\r
2746 delete [] pPalette;
\r
2753 ////////////////////////////////////////////////////////////////////////////////
\r
2755 * Apply a look up table to the image.
\r
2756 * \param pLut: BYTE[256] look up table
\r
2757 * \return true if everything is ok
\r
2759 bool CxImage::Lut(BYTE* pLut)
\r
2761 if (!pDib || !pLut) return false;
\r
2765 if (head.biClrUsed==0){
\r
2767 long xmin,xmax,ymin,ymax;
\r
2769 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2770 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2772 // faster loop for full image
\r
2773 BYTE *iSrc=info.pImage;
\r
2774 for(unsigned long i=0; i < head.biSizeImage ; i++){
\r
2775 *iSrc++ = pLut[*iSrc];
\r
2780 if (xmin==xmax || ymin==ymax)
\r
2783 dbScaler = 100.0/(ymax-ymin);
\r
2785 for(long y=ymin; y<ymax; y++){
\r
2786 info.nProgress = (long)((y-ymin)*dbScaler); //<Anatoly Ivasyuk>
\r
2787 for(long x=xmin; x<xmax; x++){
\r
2788 #if CXIMAGE_SUPPORT_SELECTION
\r
2789 if (BlindSelectionIsInside(x,y))
\r
2790 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2792 color = BlindGetPixelColor(x,y);
\r
2793 color.rgbRed = pLut[color.rgbRed];
\r
2794 color.rgbGreen = pLut[color.rgbGreen];
\r
2795 color.rgbBlue = pLut[color.rgbBlue];
\r
2796 BlindSetPixelColor(x,y,color);
\r
2800 #if CXIMAGE_SUPPORT_SELECTION
\r
2801 } else if (pSelection && (head.biBitCount==8) && IsGrayScale()){
\r
2802 long xmin,xmax,ymin,ymax;
\r
2803 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2804 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2806 if (xmin==xmax || ymin==ymax)
\r
2809 dbScaler = 100.0/(ymax-ymin);
\r
2810 for(long y=ymin; y<ymax; y++){
\r
2811 info.nProgress = (long)((y-ymin)*dbScaler);
\r
2812 for(long x=xmin; x<xmax; x++){
\r
2813 if (BlindSelectionIsInside(x,y))
\r
2815 BlindSetPixelIndex(x,y,pLut[BlindGetPixelIndex(x,y)]);
\r
2819 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2821 bool bIsGrayScale = IsGrayScale();
\r
2822 for(DWORD j=0; j<head.biClrUsed; j++){
\r
2823 color = GetPaletteColor((BYTE)j);
\r
2824 color.rgbRed = pLut[color.rgbRed];
\r
2825 color.rgbGreen = pLut[color.rgbGreen];
\r
2826 color.rgbBlue = pLut[color.rgbBlue];
\r
2827 SetPaletteColor((BYTE)j,color);
\r
2829 if (bIsGrayScale) GrayScale();
\r
2834 ////////////////////////////////////////////////////////////////////////////////
\r
2836 * Apply an indipendent look up table for each channel
\r
2837 * \param pLutR, pLutG, pLutB, pLutA: BYTE[256] look up tables
\r
2838 * \return true if everything is ok
\r
2840 bool CxImage::Lut(BYTE* pLutR, BYTE* pLutG, BYTE* pLutB, BYTE* pLutA)
\r
2842 if (!pDib || !pLutR || !pLutG || !pLutB) return false;
\r
2846 if (head.biClrUsed==0){
\r
2848 long xmin,xmax,ymin,ymax;
\r
2850 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2851 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2854 xmax = head.biWidth; ymax=head.biHeight;
\r
2857 if (xmin==xmax || ymin==ymax)
\r
2860 dbScaler = 100.0/(ymax-ymin);
\r
2862 for(long y=ymin; y<ymax; y++){
\r
2863 info.nProgress = (long)((y-ymin)*dbScaler);
\r
2864 for(long x=xmin; x<xmax; x++){
\r
2865 #if CXIMAGE_SUPPORT_SELECTION
\r
2866 if (BlindSelectionIsInside(x,y))
\r
2867 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2869 color = BlindGetPixelColor(x,y);
\r
2870 color.rgbRed = pLutR[color.rgbRed];
\r
2871 color.rgbGreen = pLutG[color.rgbGreen];
\r
2872 color.rgbBlue = pLutB[color.rgbBlue];
\r
2873 if (pLutA) color.rgbReserved=pLutA[color.rgbReserved];
\r
2874 BlindSetPixelColor(x,y,color,true);
\r
2879 bool bIsGrayScale = IsGrayScale();
\r
2880 for(DWORD j=0; j<head.biClrUsed; j++){
\r
2881 color = GetPaletteColor((BYTE)j);
\r
2882 color.rgbRed = pLutR[color.rgbRed];
\r
2883 color.rgbGreen = pLutG[color.rgbGreen];
\r
2884 color.rgbBlue = pLutB[color.rgbBlue];
\r
2885 SetPaletteColor((BYTE)j,color);
\r
2887 if (bIsGrayScale) GrayScale();
\r
2893 ////////////////////////////////////////////////////////////////////////////////
\r
2895 * Use the RedEyeRemove function to remove the red-eye effect that frequently
\r
2896 * occurs in photographs of humans and animals. You must select the region
\r
2897 * where the function will filter the red channel.
\r
2898 * \param strength: range from 0.0f (no effect) to 1.0f (full effect). Default = 0.8
\r
2899 * \return true if everything is ok
\r
2901 bool CxImage::RedEyeRemove(float strength)
\r
2903 if (!pDib) return false;
\r
2906 long xmin,xmax,ymin,ymax;
\r
2908 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2909 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2912 xmax = head.biWidth; ymax=head.biHeight;
\r
2915 if (xmin==xmax || ymin==ymax)
\r
2918 if (strength<0.0f) strength = 0.0f;
\r
2919 if (strength>1.0f) strength = 1.0f;
\r
2921 for(long y=ymin; y<ymax; y++){
\r
2922 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
2923 if (info.nEscape) break;
\r
2924 for(long x=xmin; x<xmax; x++){
\r
2925 #if CXIMAGE_SUPPORT_SELECTION
\r
2926 if (BlindSelectionIsInside(x,y))
\r
2927 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2929 float a = 1.0f-5.0f*((float)((x-0.5f*(xmax+xmin))*(x-0.5f*(xmax+xmin))+(y-0.5f*(ymax+ymin))*(y-0.5f*(ymax+ymin))))/((float)((xmax-xmin)*(ymax-ymin)));
\r
2931 color = BlindGetPixelColor(x,y);
\r
2932 color.rgbRed = (BYTE)(a*min(color.rgbGreen,color.rgbBlue)+(1.0f-a)*color.rgbRed);
\r
2933 BlindSetPixelColor(x,y,color);
\r
2939 ////////////////////////////////////////////////////////////////////////////////
\r
2941 * Changes the saturation of the image.
\r
2942 * \param saturation: can be from -100 to 100, positive values increase the saturation.
\r
2943 * \param colorspace: can be 1 (HSL) or 2 (YUV).
\r
2944 * \return true if everything is ok
\r
2946 bool CxImage::Saturate(const long saturation, const long colorspace)
\r
2951 long xmin,xmax,ymin,ymax;
\r
2953 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
2954 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
2957 xmax = head.biWidth; ymax=head.biHeight;
\r
2960 if (xmin==xmax || ymin==ymax)
\r
2965 switch(colorspace)
\r
2969 for (int i=0;i<256;i++) {
\r
2970 cTable[i] = (BYTE)max(0,min(255,(int)(i + saturation)));
\r
2972 for(long y=ymin; y<ymax; y++){
\r
2973 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
2974 if (info.nEscape) break;
\r
2975 for(long x=xmin; x<xmax; x++){
\r
2976 #if CXIMAGE_SUPPORT_SELECTION
\r
2977 if (BlindSelectionIsInside(x,y))
\r
2978 #endif //CXIMAGE_SUPPORT_SELECTION
\r
2980 RGBQUAD c = RGBtoHSL(BlindGetPixelColor(x,y));
\r
2981 c.rgbGreen = cTable[c.rgbGreen];
\r
2983 BlindSetPixelColor(x,y,c);
\r
2991 for (int i=0;i<256;i++) {
\r
2992 cTable[i] = (BYTE)max(0,min(255,(int)((i-128)*(100 + saturation)/100.0f + 128.5f)));
\r
2994 for(long y=ymin; y<ymax; y++){
\r
2995 info.nProgress = (long)(100*(y-ymin)/(ymax-ymin));
\r
2996 if (info.nEscape) break;
\r
2997 for(long x=xmin; x<xmax; x++){
\r
2998 #if CXIMAGE_SUPPORT_SELECTION
\r
2999 if (BlindSelectionIsInside(x,y))
\r
3000 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3002 RGBQUAD c = RGBtoYUV(BlindGetPixelColor(x,y));
\r
3003 c.rgbGreen = cTable[c.rgbGreen];
\r
3004 c.rgbBlue = cTable[c.rgbBlue];
\r
3006 BlindSetPixelColor(x,y,c);
\r
3013 strcpy(info.szLastError,"Saturate: wrong colorspace");
\r
3019 ////////////////////////////////////////////////////////////////////////////////
\r
3021 * Solarize: convert all colors above a given lightness level into their negative
\r
3022 * \param level : lightness threshold. Range = 0 to 255; default = 128.
\r
3023 * \param bLinkedChannels: true = compare with luminance, preserve colors (default)
\r
3024 * false = compare with independent R,G,B levels
\r
3025 * \return true if everything is ok
\r
3026 * \author [Priyank Bolia] (priyank_bolia(at)yahoo(dot)com); changes [DP]
\r
3028 bool CxImage::Solarize(BYTE level, bool bLinkedChannels)
\r
3030 if (!pDib) return false;
\r
3032 long xmin,xmax,ymin,ymax;
\r
3034 xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
\r
3035 ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
\r
3038 xmax = head.biWidth; ymax=head.biHeight;
\r
3041 if (head.biBitCount<=8){
\r
3042 if (IsGrayScale()){ //GRAYSCALE, selection
\r
3043 for(long y=ymin; y<ymax; y++){
\r
3044 for(long x=xmin; x<xmax; x++){
\r
3045 #if CXIMAGE_SUPPORT_SELECTION
\r
3046 if (BlindSelectionIsInside(x,y))
\r
3047 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3049 BYTE index = BlindGetPixelIndex(x,y);
\r
3050 RGBQUAD color = GetPaletteColor(index);
\r
3051 if ((BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue)>level){
\r
3052 BlindSetPixelIndex(x,y,255-index);
\r
3057 } else { //PALETTE, full image
\r
3058 RGBQUAD* ppal=GetPalette();
\r
3059 for(DWORD i=0;i<head.biClrUsed;i++){
\r
3060 RGBQUAD color = GetPaletteColor((BYTE)i);
\r
3061 if (bLinkedChannels){
\r
3062 if ((BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue)>level){
\r
3063 ppal[i].rgbBlue =(BYTE)(255-ppal[i].rgbBlue);
\r
3064 ppal[i].rgbGreen =(BYTE)(255-ppal[i].rgbGreen);
\r
3065 ppal[i].rgbRed =(BYTE)(255-ppal[i].rgbRed);
\r
3068 if (color.rgbBlue>level) ppal[i].rgbBlue =(BYTE)(255-ppal[i].rgbBlue);
\r
3069 if (color.rgbGreen>level) ppal[i].rgbGreen =(BYTE)(255-ppal[i].rgbGreen);
\r
3070 if (color.rgbRed>level) ppal[i].rgbRed =(BYTE)(255-ppal[i].rgbRed);
\r
3074 } else { //RGB, selection
\r
3075 for(long y=ymin; y<ymax; y++){
\r
3076 for(long x=xmin; x<xmax; x++){
\r
3077 #if CXIMAGE_SUPPORT_SELECTION
\r
3078 if (BlindSelectionIsInside(x,y))
\r
3079 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3081 RGBQUAD color = BlindGetPixelColor(x,y);
\r
3082 if (bLinkedChannels){
\r
3083 if ((BYTE)RGB2GRAY(color.rgbRed,color.rgbGreen,color.rgbBlue)>level){
\r
3084 color.rgbRed = (BYTE)(255-color.rgbRed);
\r
3085 color.rgbGreen = (BYTE)(255-color.rgbGreen);
\r
3086 color.rgbBlue = (BYTE)(255-color.rgbBlue);
\r
3089 if (color.rgbBlue>level) color.rgbBlue =(BYTE)(255-color.rgbBlue);
\r
3090 if (color.rgbGreen>level) color.rgbGreen =(BYTE)(255-color.rgbGreen);
\r
3091 if (color.rgbRed>level) color.rgbRed =(BYTE)(255-color.rgbRed);
\r
3093 BlindSetPixelColor(x,y,color);
\r
3099 //invert transparent color only in case of full image processing
\r
3100 if (pSelection==0 || (!IsGrayScale() && IsIndexed())){
\r
3101 if (bLinkedChannels){
\r
3102 if ((BYTE)RGB2GRAY(info.nBkgndColor.rgbRed,info.nBkgndColor.rgbGreen,info.nBkgndColor.rgbBlue)>level){
\r
3103 info.nBkgndColor.rgbBlue = (BYTE)(255-info.nBkgndColor.rgbBlue);
\r
3104 info.nBkgndColor.rgbGreen = (BYTE)(255-info.nBkgndColor.rgbGreen);
\r
3105 info.nBkgndColor.rgbRed = (BYTE)(255-info.nBkgndColor.rgbRed);
\r
3108 if (info.nBkgndColor.rgbBlue>level) info.nBkgndColor.rgbBlue = (BYTE)(255-info.nBkgndColor.rgbBlue);
\r
3109 if (info.nBkgndColor.rgbGreen>level) info.nBkgndColor.rgbGreen = (BYTE)(255-info.nBkgndColor.rgbGreen);
\r
3110 if (info.nBkgndColor.rgbRed>level) info.nBkgndColor.rgbRed = (BYTE)(255-info.nBkgndColor.rgbRed);
\r
3117 ////////////////////////////////////////////////////////////////////////////////
\r
3119 * Converts the RGB triplets to and from different colorspace
\r
3120 * \param dstColorSpace: destination colorspace; 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ
\r
3121 * \param srcColorSpace: source colorspace; 0 = RGB, 1 = HSL, 2 = YUV, 3 = YIQ, 4 = XYZ
\r
3122 * \return true if everything is ok
\r
3124 bool CxImage::ConvertColorSpace(const long dstColorSpace, const long srcColorSpace)
\r
3129 if (dstColorSpace == srcColorSpace)
\r
3132 long w = GetWidth();
\r
3133 long h = GetHeight();
\r
3135 for (long y=0;y<h;y++){
\r
3136 info.nProgress = (long)(100*y/h);
\r
3137 if (info.nEscape) break;
\r
3138 for (long x=0;x<w;x++){
\r
3139 RGBQUAD c = BlindGetPixelColor(x,y);
\r
3140 switch (srcColorSpace){
\r
3156 strcpy(info.szLastError,"ConvertColorSpace: unknown source colorspace");
\r
3159 switch (dstColorSpace){
\r
3175 strcpy(info.szLastError,"ConvertColorSpace: unknown destination colorspace");
\r
3178 BlindSetPixelColor(x,y,c);
\r
3183 ////////////////////////////////////////////////////////////////////////////////
\r
3185 * Finds the optimal (global or local) treshold for image binarization
\r
3186 * \param method: 0 = average all methods (default); 1 = Otsu; 2 = Kittler & Illingworth; 3 = max entropy; 4 = potential difference;
\r
3187 * \param pBox: region from where the threshold is computed; 0 = full image (default).
\r
3188 * \param pContrastMask: limit the computation only in regions with contrasted (!=0) pixels; default = 0.
\r
3189 * the pContrastMask image must be grayscale with same with and height of the current image,
\r
3190 * can be obtained from the current image with a filter:
\r
3191 * CxImage iContrastMask(*image,true,false,false);
\r
3192 * iContrastMask.GrayScale();
\r
3193 * long edge[]={-1,-1,-1,-1,8,-1,-1,-1,-1};
\r
3194 * iContrastMask.Filter(edge,3,1,0);
\r
3195 * long blur[]={1,1,1,1,1,1,1,1,1};
\r
3196 * iContrastMask.Filter(blur,3,9,0);
\r
3197 * \return optimal threshold; -1 = error.
\r
3198 * \sa AdaptiveThreshold
\r
3200 int CxImage::OptimalThreshold(long method, RECT * pBox, CxImage* pContrastMask)
\r
3205 if (head.biBitCount!=8){
\r
3206 strcpy(info.szLastError,"OptimalThreshold works only on 8 bit images");
\r
3210 if (pContrastMask){
\r
3211 if (!pContrastMask->IsValid() ||
\r
3212 !pContrastMask->IsGrayScale() ||
\r
3213 pContrastMask->GetWidth() != GetWidth() ||
\r
3214 pContrastMask->GetHeight() != GetHeight()){
\r
3215 strcpy(info.szLastError,"OptimalThreshold invalid ContrastMask");
\r
3220 long xmin,xmax,ymin,ymax;
\r
3222 xmin = max(pBox->left,0);
\r
3223 xmax = min(pBox->right,head.biWidth);
\r
3224 ymin = max(pBox->bottom,0);
\r
3225 ymax = min(pBox->top,head.biHeight);
\r
3228 xmax = head.biWidth; ymax=head.biHeight;
\r
3231 if (xmin>=xmax || ymin>=ymax)
\r
3235 memset(p, 0, 256*sizeof(double));
\r
3237 for (long y = ymin; y<ymax; y++){
\r
3238 BYTE* pGray = GetBits(y) + xmin;
\r
3240 if (pContrastMask) pContr = pContrastMask->GetBits(y) + xmin;
\r
3241 for (long x = xmin; x<xmax; x++){
\r
3242 BYTE n = *pGray++;
\r
3244 if (*pContr) p[n]++;
\r
3252 //find histogram limits
\r
3254 while (gray_min<255 && p[gray_min]==0) gray_min++;
\r
3255 int gray_max = 255;
\r
3256 while (gray_max>0 && p[gray_max]==0) gray_max--;
\r
3257 if (gray_min > gray_max)
\r
3259 if (gray_min == gray_max){
\r
3260 if (gray_min == 0)
\r
3263 return gray_max-1;
\r
3266 //compute total moments 0th,1st,2nd order
\r
3271 for (i = gray_min; i <= gray_max; i++){
\r
3274 q_tot += i*i*p[i];
\r
3277 double L, L1max, L2max, L3max, L4max; //objective functions
\r
3278 int th1,th2,th3,th4; //optimal thresholds
\r
3279 L1max = L2max = L3max = L4max = 0;
\r
3280 th1 = th2 = th3 = th4 = -1;
\r
3282 double w1, w2, m1, m2, q1, q2, s1, s2;
\r
3284 for (i = gray_min; i < gray_max; i++){
\r
3291 s1 = q1/w1-m1*m1/w1/w1; //s1 = q1/w1-pow(m1/w1,2);
\r
3292 s2 = q2/w2-m2*m2/w2/w2; //s2 = q2/w2-pow(m2/w2,2);
\r
3295 L = -(s1*w1 + s2*w2); //implemented as definition
\r
3296 //L = w1 * w2 * (m2/w2 - m1/w1)*(m2/w2 - m1/w1); //implementation that doesn't need s1 & s2
\r
3297 if (L1max < L || th1<0){
\r
3302 //Kittler and Illingworth
\r
3303 if (s1>0 && s2>0){
\r
3304 L = w1*log(w1/sqrt(s1))+w2*log(w2/sqrt(s2));
\r
3305 //L = w1*log(w1*w1/s1)+w2*log(w2*w2/s2);
\r
3306 if (L2max < L || th2<0){
\r
3314 for (k=gray_min;k<=i;k++) if (p[k] > 0) L -= p[k]*log(p[k]/w1)/w1;
\r
3315 for (k;k<=gray_max;k++) if (p[k] > 0) L -= p[k]*log(p[k]/w2)/w2;
\r
3316 if (L3max < L || th3<0){
\r
3321 //potential difference (based on Electrostatic Binarization method by J. Acharya & G. Sreechakra)
\r
3322 // L=-fabs(vdiff/vsum); è molto selettivo, sembra che L=-fabs(vdiff) o L=-(vsum)
\r
3323 // abbiano lo stesso valore di soglia... il che semplificherebbe molto la routine
\r
3325 for (k=gray_min;k<=i;k++)
\r
3326 vdiff += p[k]*(i-k)*(i-k);
\r
3327 double vsum = vdiff;
\r
3328 for (k;k<=gray_max;k++){
\r
3329 double dv = p[k]*(k-i)*(k-i);
\r
3333 if (vsum>0) L = -fabs(vdiff/vsum); else L = 0;
\r
3334 if (L4max < L || th4<0){
\r
3345 case 2: //Kittler and Illingworth
\r
3348 case 3: //max entropy
\r
3351 case 4: //potential difference
\r
3358 if (th1>=0) { threshold += th1; nt++;}
\r
3359 if (th2>=0) { threshold += th2; nt++;}
\r
3360 if (th3>=0) { threshold += th3; nt++;}
\r
3361 if (th4>=0) { threshold += th4; nt++;}
\r
3365 threshold = (gray_min+gray_max)/2;
\r
3367 /*better(?) but really expensive alternative:
\r
3369 pth1 = c1(th1)/sqrt(2*pi*s1(th1))*exp(-((n - m1(th1)).^2)/2/s1(th1)) + c2(th1)/sqrt(2*pi*s2(th1))*exp(-((n - m2(th1)).^2)/2/s2(th1));
\r
3370 pth2 = c1(th2)/sqrt(2*pi*s1(th2))*exp(-((n - m1(th2)).^2)/2/s1(th2)) + c2(th2)/sqrt(2*pi*s2(th2))*exp(-((n - m2(th2)).^2)/2/s2(th2));
\r
3372 mse_th1 = sum((p-pth1).^2);
\r
3373 mse_th2 = sum((p-pth2).^2);
\r
3375 select th# that gives minimum mse_th#
\r
3381 if (threshold <= gray_min || threshold >= gray_max)
\r
3382 threshold = (gray_min+gray_max)/2;
\r
3386 ///////////////////////////////////////////////////////////////////////////////
\r
3388 * Converts the image to B&W, using an optimal threshold mask
\r
3389 * \param method: 0 = average all methods (default); 1 = Otsu; 2 = Kittler & Illingworth; 3 = max entropy; 4 = potential difference;
\r
3390 * \param nBoxSize: the image is divided into "nBoxSize x nBoxSize" blocks, from where the threshold is computed; min = 8; default = 64.
\r
3391 * \param pContrastMask: limit the computation only in regions with contrasted (!=0) pixels; default = 0.
\r
3392 * \param nBias: global offset added to the threshold mask; default = 0.
\r
3393 * \param fGlobalLocalBalance: balance between local and global threshold. default = 0.5
\r
3394 * fGlobalLocalBalance can be from 0.0 (use only local threshold) to 1.0 (use only global threshold)
\r
3395 * the pContrastMask image must be grayscale with same with and height of the current image,
\r
3396 * \return true if everything is ok.
\r
3397 * \sa OptimalThreshold
\r
3399 bool CxImage::AdaptiveThreshold(long method, long nBoxSize, CxImage* pContrastMask, long nBias, float fGlobalLocalBalance)
\r
3404 if (pContrastMask){
\r
3405 if (!pContrastMask->IsValid() ||
\r
3406 !pContrastMask->IsGrayScale() ||
\r
3407 pContrastMask->GetWidth() != GetWidth() ||
\r
3408 pContrastMask->GetHeight() != GetHeight()){
\r
3409 strcpy(info.szLastError,"AdaptiveThreshold invalid ContrastMask");
\r
3414 if (nBoxSize<8) nBoxSize = 8;
\r
3415 if (fGlobalLocalBalance<0.0f) fGlobalLocalBalance = 0.0f;
\r
3416 if (fGlobalLocalBalance>1.0f) fGlobalLocalBalance = 1.0f;
\r
3418 long mw = (head.biWidth + nBoxSize - 1)/nBoxSize;
\r
3419 long mh = (head.biHeight + nBoxSize - 1)/nBoxSize;
\r
3421 CxImage mask(mw,mh,8);
\r
3422 if(!mask.GrayScale())
\r
3428 int globalthreshold = OptimalThreshold(method, 0, pContrastMask);
\r
3429 if (globalthreshold <0)
\r
3432 for (long y=0; y<mh; y++){
\r
3433 for (long x=0; x<mw; x++){
\r
3434 info.nProgress = (long)(100*(x+y*mw)/(mw*mh));
\r
3435 if (info.nEscape) break;
\r
3437 r.left = x*nBoxSize;
\r
3438 r.right = r.left + nBoxSize;
\r
3439 r.bottom = y*nBoxSize;
\r
3440 r.top = r.bottom + nBoxSize;
\r
3441 int threshold = OptimalThreshold(method, &r, pContrastMask);
\r
3442 if (threshold <0) return false;
\r
3443 mask.SetPixelIndex(x,y,(BYTE)max(0,min(255,nBias+((1.0f-fGlobalLocalBalance)*threshold + fGlobalLocalBalance*globalthreshold))));
\r
3447 mask.Resample(mw*nBoxSize,mh*nBoxSize,0);
\r
3448 mask.Crop(0,head.biHeight,head.biWidth,0);
\r
3450 if(!Threshold(&mask))
\r
3456 ////////////////////////////////////////////////////////////////////////////////
\r
3458 ////////////////////////////////////////////////////////////////////////////////
\r
3461 * \param xStart, yStart: starting point
\r
3462 * \param cFillColor: filling color
\r
3463 * \param nTolerance: deviation from the starting point color
\r
3464 * \param nOpacity: can be from 0 (transparent) to 255 (opaque, default)
\r
3465 * \param bSelectFilledArea: if true, the pixels in the region are also set in the selection layer; default = false
\r
3466 * \param nSelectionLevel: if bSelectFilledArea is true, the selected pixels are set to nSelectionLevel; default = 255
\r
3467 * Note: nOpacity=0 && bSelectFilledArea=true act as a "magic wand"
\r
3468 * \return true if everything is ok
\r
3470 bool CxImage::FloodFill(const long xStart, const long yStart, const RGBQUAD cFillColor, const BYTE nTolerance,
\r
3471 BYTE nOpacity, const bool bSelectFilledArea, const BYTE nSelectionLevel)
\r
3476 if (!IsInside(xStart,yStart))
\r
3479 #if CXIMAGE_SUPPORT_SELECTION
\r
3480 if (!SelectionIsInside(xStart,yStart))
\r
3482 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3484 RGBQUAD* pPalette=NULL;
\r
3485 WORD bpp = GetBpp();
\r
3486 //nTolerance or nOpacity implemented only for grayscale or 24bpp images
\r
3487 if ((nTolerance || nOpacity != 255) && !(head.biBitCount == 24 || IsGrayScale())){
\r
3488 pPalette = new RGBQUAD[head.biClrUsed];
\r
3489 memcpy(pPalette, GetPalette(),GetPaletteSize());
\r
3490 if (!IncreaseBpp(24))
\r
3494 BYTE* pFillMask = (BYTE*)calloc(head.biWidth * head.biHeight,1);
\r
3498 //------------------------------------- Begin of Flood Fill
\r
3499 POINT offset[4] = {{-1,0},{0,-1},{1,0},{0,1}};
\r
3500 std::queue<POINT> q;
\r
3501 POINT point = {xStart,yStart};
\r
3504 if (IsIndexed()){ //--- Generic indexed image, no tolerance OR Grayscale image with tolerance
\r
3505 BYTE idxRef = GetPixelIndex(xStart,yStart);
\r
3506 BYTE idxFill = GetNearestIndex(cFillColor);
\r
3507 BYTE idxMin = (BYTE)min(255, max(0,(int)(idxRef - nTolerance)));
\r
3508 BYTE idxMax = (BYTE)min(255, max(0,(int)(idxRef + nTolerance)));
\r
3512 point = q.front();
\r
3515 for (int z=0; z<4; z++){
\r
3516 int x = point.x + offset[z].x;
\r
3517 int y = point.y + offset[z].y;
\r
3518 if(IsInside(x,y)){
\r
3519 #if CXIMAGE_SUPPORT_SELECTION
\r
3520 if (BlindSelectionIsInside(x,y))
\r
3521 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3523 BYTE idx = BlindGetPixelIndex(x, y);
\r
3524 BYTE* pFill = pFillMask + x + y * head.biWidth;
\r
3525 if (*pFill==0 && idxMin <= idx && idx <= idxMax )
\r
3528 if (nOpacity == 255)
\r
3529 BlindSetPixelIndex(x, y, idxFill);
\r
3531 BlindSetPixelIndex(x, y, (BYTE)((idxFill * nOpacity + idx * (255-nOpacity))>>8));
\r
3541 } else { //--- RGB image
\r
3542 RGBQUAD cRef = GetPixelColor(xStart,yStart);
\r
3543 RGBQUAD cRefMin, cRefMax;
\r
3544 cRefMin.rgbRed = (BYTE)min(255, max(0,(int)(cRef.rgbRed - nTolerance)));
\r
3545 cRefMin.rgbGreen = (BYTE)min(255, max(0,(int)(cRef.rgbGreen - nTolerance)));
\r
3546 cRefMin.rgbBlue = (BYTE)min(255, max(0,(int)(cRef.rgbBlue - nTolerance)));
\r
3547 cRefMax.rgbRed = (BYTE)min(255, max(0,(int)(cRef.rgbRed + nTolerance)));
\r
3548 cRefMax.rgbGreen = (BYTE)min(255, max(0,(int)(cRef.rgbGreen + nTolerance)));
\r
3549 cRefMax.rgbBlue = (BYTE)min(255, max(0,(int)(cRef.rgbBlue + nTolerance)));
\r
3553 point = q.front();
\r
3556 for (int z=0; z<4; z++){
\r
3557 int x = point.x + offset[z].x;
\r
3558 int y = point.y + offset[z].y;
\r
3559 if(IsInside(x,y)){
\r
3560 #if CXIMAGE_SUPPORT_SELECTION
\r
3561 if (BlindSelectionIsInside(x,y))
\r
3562 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3564 RGBQUAD cc = BlindGetPixelColor(x, y);
\r
3565 BYTE* pFill = pFillMask + x + y * head.biWidth;
\r
3567 cRefMin.rgbRed <= cc.rgbRed && cc.rgbRed <= cRefMax.rgbRed &&
\r
3568 cRefMin.rgbGreen <= cc.rgbGreen && cc.rgbGreen <= cRefMax.rgbGreen &&
\r
3569 cRefMin.rgbBlue <= cc.rgbBlue && cc.rgbBlue <= cRefMax.rgbBlue )
\r
3572 if (nOpacity == 255)
\r
3573 BlindSetPixelColor(x, y, cFillColor);
\r
3576 cc.rgbRed = (BYTE)((cFillColor.rgbRed * nOpacity + cc.rgbRed * (255-nOpacity))>>8);
\r
3577 cc.rgbGreen = (BYTE)((cFillColor.rgbGreen * nOpacity + cc.rgbGreen * (255-nOpacity))>>8);
\r
3578 cc.rgbBlue = (BYTE)((cFillColor.rgbBlue * nOpacity + cc.rgbBlue * (255-nOpacity))>>8);
\r
3579 BlindSetPixelColor(x, y, cc);
\r
3591 if (pFillMask[xStart+yStart*head.biWidth] == 0 && nOpacity>0){
\r
3592 if (nOpacity == 255)
\r
3593 BlindSetPixelColor(xStart, yStart, cFillColor);
\r
3596 RGBQUAD cc = BlindGetPixelColor(xStart, yStart);
\r
3597 cc.rgbRed = (BYTE)((cFillColor.rgbRed * nOpacity + cc.rgbRed * (255-nOpacity))>>8);
\r
3598 cc.rgbGreen = (BYTE)((cFillColor.rgbGreen * nOpacity + cc.rgbGreen * (255-nOpacity))>>8);
\r
3599 cc.rgbBlue = (BYTE)((cFillColor.rgbBlue * nOpacity + cc.rgbBlue * (255-nOpacity))>>8);
\r
3600 BlindSetPixelColor(xStart, yStart, cc);
\r
3603 pFillMask[xStart+yStart*head.biWidth] = 1;
\r
3604 //------------------------------------- End of Flood Fill
\r
3606 //if necessary, restore the original BPP and palette
\r
3608 DecreaseBpp(bpp, false, pPalette);
\r
3609 delete [] pPalette;
\r
3612 #if CXIMAGE_SUPPORT_SELECTION
\r
3613 if (bSelectFilledArea){
\r
3614 if (!SelectionIsValid()){
\r
3615 if (!SelectionCreate()){
\r
3619 info.rSelectionBox.right = head.biWidth;
\r
3620 info.rSelectionBox.top = head.biHeight;
\r
3621 info.rSelectionBox.left = info.rSelectionBox.bottom = 0;
\r
3624 SelectionGetBox(r);
\r
3625 for (long y = r.bottom; y < r.top; y++){
\r
3626 BYTE* pFill = pFillMask + r.left + y * head.biWidth;
\r
3627 for (long x = r.left; x<r.right; x++){
\r
3628 if (*pFill) SelectionSet(x,y,nSelectionLevel);
\r
3632 SelectionRebuildBox();
\r
3634 #endif //CXIMAGE_SUPPORT_SELECTION
\r
3641 ////////////////////////////////////////////////////////////////////////////////
\r
3642 #endif //CXIMAGE_SUPPORT_DSP
\r