/src/freeimage-svn/FreeImage/trunk/Source/FreeImage/NNQuantizer.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // NeuQuant Neural-Net Quantization Algorithm |
2 | | // ------------------------------------------ |
3 | | // |
4 | | // Copyright (c) 1994 Anthony Dekker |
5 | | // |
6 | | // NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. |
7 | | // See "Kohonen neural networks for optimal colour quantization" |
8 | | // in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. |
9 | | // for a discussion of the algorithm. |
10 | | // |
11 | | // Any party obtaining a copy of these files from the author, directly or |
12 | | // indirectly, is granted, free of charge, a full and unrestricted irrevocable, |
13 | | // world-wide, paid up, royalty-free, nonexclusive right and license to deal |
14 | | // in this software and documentation files (the "Software"), including without |
15 | | // limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, |
16 | | // and/or sell copies of the Software, and to permit persons who receive |
17 | | // copies from any such party to do so, with the only requirement being |
18 | | // that this copyright notice remain intact. |
19 | | |
20 | | /////////////////////////////////////////////////////////////////////// |
21 | | // History |
22 | | // ------- |
23 | | // January 2001: Adaptation of the Neural-Net Quantization Algorithm |
24 | | // for the FreeImage 2 library |
25 | | // Author: Hervé Drolon (drolon@infonie.fr) |
26 | | // March 2004: Adaptation for the FreeImage 3 library (port to big endian processors) |
27 | | // Author: Hervé Drolon (drolon@infonie.fr) |
28 | | // April 2004: Algorithm rewritten as a C++ class. |
29 | | // Fixed a bug in the algorithm with handling of 4-byte boundary alignment. |
30 | | // Author: Hervé Drolon (drolon@infonie.fr) |
31 | | /////////////////////////////////////////////////////////////////////// |
32 | | |
33 | | #include "Quantizers.h" |
34 | | #include "FreeImage.h" |
35 | | #include "Utilities.h" |
36 | | |
37 | | |
38 | | // Four primes near 500 - assume no image has a length so large |
39 | | // that it is divisible by all four primes |
40 | | // ========================================================== |
41 | | |
42 | 0 | #define prime1 499 |
43 | 0 | #define prime2 491 |
44 | 0 | #define prime3 487 |
45 | 0 | #define prime4 503 |
46 | | |
47 | | // ---------------------------------------------------------------- |
48 | | |
49 | | NNQuantizer::NNQuantizer(int PaletteSize) |
50 | 0 | { |
51 | 0 | netsize = PaletteSize; |
52 | 0 | maxnetpos = netsize - 1; |
53 | 0 | initrad = netsize < 8 ? 1 : (netsize >> 3); |
54 | 0 | initradius = (initrad * radiusbias); |
55 | |
|
56 | 0 | network = NULL; |
57 | |
|
58 | 0 | network = (pixel *)malloc(netsize * sizeof(pixel)); |
59 | 0 | bias = (int *)malloc(netsize * sizeof(int)); |
60 | 0 | freq = (int *)malloc(netsize * sizeof(int)); |
61 | 0 | radpower = (int *)malloc(initrad * sizeof(int)); |
62 | |
|
63 | 0 | if( !network || !bias || !freq || !radpower ) { |
64 | 0 | if(network) free(network); |
65 | 0 | if(bias) free(bias); |
66 | 0 | if(freq) free(freq); |
67 | 0 | if(radpower) free(radpower); |
68 | 0 | throw FI_MSG_ERROR_MEMORY; |
69 | 0 | } |
70 | 0 | } |
71 | | |
72 | | NNQuantizer::~NNQuantizer() |
73 | 0 | { |
74 | 0 | if(network) free(network); |
75 | 0 | if(bias) free(bias); |
76 | 0 | if(freq) free(freq); |
77 | 0 | if(radpower) free(radpower); |
78 | 0 | } |
79 | | |
80 | | /////////////////////////////////////////////////////////////////////////// |
81 | | // Initialise network in range (0,0,0) to (255,255,255) and set parameters |
82 | | // ----------------------------------------------------------------------- |
83 | | |
84 | 0 | void NNQuantizer::initnet() { |
85 | 0 | int i, *p; |
86 | |
|
87 | 0 | for (i = 0; i < netsize; i++) { |
88 | 0 | p = network[i]; |
89 | 0 | p[FI_RGBA_BLUE] = p[FI_RGBA_GREEN] = p[FI_RGBA_RED] = (i << (netbiasshift+8))/netsize; |
90 | 0 | freq[i] = intbias/netsize; /* 1/netsize */ |
91 | 0 | bias[i] = 0; |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | /////////////////////////////////////////////////////////////////////////////////////// |
96 | | // Unbias network to give byte values 0..255 and record position i to prepare for sort |
97 | | // ------------------------------------------------------------------------------------ |
98 | | |
99 | 0 | void NNQuantizer::unbiasnet() { |
100 | 0 | int i, j, temp; |
101 | |
|
102 | 0 | for (i = 0; i < netsize; i++) { |
103 | 0 | for (j = 0; j < 3; j++) { |
104 | | // OLD CODE: network[i][j] >>= netbiasshift; |
105 | | // Fix based on bug report by Juergen Weigert jw@suse.de |
106 | 0 | temp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift; |
107 | 0 | if (temp > 255) temp = 255; |
108 | 0 | network[i][j] = temp; |
109 | 0 | } |
110 | 0 | network[i][3] = i; // record colour no |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | ////////////////////////////////////////////////////////////////////////////////// |
115 | | // Insertion sort of network and building of netindex[0..255] (to do after unbias) |
116 | | // ------------------------------------------------------------------------------- |
117 | | |
118 | 0 | void NNQuantizer::inxbuild() { |
119 | 0 | int i,j,smallpos,smallval; |
120 | 0 | int *p,*q; |
121 | 0 | int previouscol,startpos; |
122 | |
|
123 | 0 | previouscol = 0; |
124 | 0 | startpos = 0; |
125 | 0 | for (i = 0; i < netsize; i++) { |
126 | 0 | p = network[i]; |
127 | 0 | smallpos = i; |
128 | 0 | smallval = p[FI_RGBA_GREEN]; // index on g |
129 | | // find smallest in i..netsize-1 |
130 | 0 | for (j = i+1; j < netsize; j++) { |
131 | 0 | q = network[j]; |
132 | 0 | if (q[FI_RGBA_GREEN] < smallval) { // index on g |
133 | 0 | smallpos = j; |
134 | 0 | smallval = q[FI_RGBA_GREEN]; // index on g |
135 | 0 | } |
136 | 0 | } |
137 | 0 | q = network[smallpos]; |
138 | | // swap p (i) and q (smallpos) entries |
139 | 0 | if (i != smallpos) { |
140 | 0 | j = q[FI_RGBA_BLUE]; q[FI_RGBA_BLUE] = p[FI_RGBA_BLUE]; p[FI_RGBA_BLUE] = j; |
141 | 0 | j = q[FI_RGBA_GREEN]; q[FI_RGBA_GREEN] = p[FI_RGBA_GREEN]; p[FI_RGBA_GREEN] = j; |
142 | 0 | j = q[FI_RGBA_RED]; q[FI_RGBA_RED] = p[FI_RGBA_RED]; p[FI_RGBA_RED] = j; |
143 | 0 | j = q[3]; q[3] = p[3]; p[3] = j; |
144 | 0 | } |
145 | | // smallval entry is now in position i |
146 | 0 | if (smallval != previouscol) { |
147 | 0 | netindex[previouscol] = (startpos+i)>>1; |
148 | 0 | for (j = previouscol+1; j < smallval; j++) |
149 | 0 | netindex[j] = i; |
150 | 0 | previouscol = smallval; |
151 | 0 | startpos = i; |
152 | 0 | } |
153 | 0 | } |
154 | 0 | netindex[previouscol] = (startpos+maxnetpos)>>1; |
155 | 0 | for (j = previouscol+1; j < 256; j++) |
156 | 0 | netindex[j] = maxnetpos; // really 256 |
157 | 0 | } |
158 | | |
159 | | /////////////////////////////////////////////////////////////////////////////// |
160 | | // Search for BGR values 0..255 (after net is unbiased) and return colour index |
161 | | // ---------------------------------------------------------------------------- |
162 | | |
163 | 0 | int NNQuantizer::inxsearch(int b, int g, int r) { |
164 | 0 | int i, j, dist, a, bestd; |
165 | 0 | int *p; |
166 | 0 | int best; |
167 | |
|
168 | 0 | bestd = 1000; // biggest possible dist is 256*3 |
169 | 0 | best = -1; |
170 | 0 | i = netindex[g]; // index on g |
171 | 0 | j = i-1; // start at netindex[g] and work outwards |
172 | |
|
173 | 0 | while ((i < netsize) || (j >= 0)) { |
174 | 0 | if (i < netsize) { |
175 | 0 | p = network[i]; |
176 | 0 | dist = p[FI_RGBA_GREEN] - g; // inx key |
177 | 0 | if (dist >= bestd) |
178 | 0 | i = netsize; // stop iter |
179 | 0 | else { |
180 | 0 | i++; |
181 | 0 | if (dist < 0) |
182 | 0 | dist = -dist; |
183 | 0 | a = p[FI_RGBA_BLUE] - b; |
184 | 0 | if (a < 0) |
185 | 0 | a = -a; |
186 | 0 | dist += a; |
187 | 0 | if (dist < bestd) { |
188 | 0 | a = p[FI_RGBA_RED] - r; |
189 | 0 | if (a<0) |
190 | 0 | a = -a; |
191 | 0 | dist += a; |
192 | 0 | if (dist < bestd) { |
193 | 0 | bestd = dist; |
194 | 0 | best = p[3]; |
195 | 0 | } |
196 | 0 | } |
197 | 0 | } |
198 | 0 | } |
199 | 0 | if (j >= 0) { |
200 | 0 | p = network[j]; |
201 | 0 | dist = g - p[FI_RGBA_GREEN]; // inx key - reverse dif |
202 | 0 | if (dist >= bestd) |
203 | 0 | j = -1; // stop iter |
204 | 0 | else { |
205 | 0 | j--; |
206 | 0 | if (dist < 0) |
207 | 0 | dist = -dist; |
208 | 0 | a = p[FI_RGBA_BLUE] - b; |
209 | 0 | if (a<0) |
210 | 0 | a = -a; |
211 | 0 | dist += a; |
212 | 0 | if (dist < bestd) { |
213 | 0 | a = p[FI_RGBA_RED] - r; |
214 | 0 | if (a<0) |
215 | 0 | a = -a; |
216 | 0 | dist += a; |
217 | 0 | if (dist < bestd) { |
218 | 0 | bestd = dist; |
219 | 0 | best = p[3]; |
220 | 0 | } |
221 | 0 | } |
222 | 0 | } |
223 | 0 | } |
224 | 0 | } |
225 | 0 | return best; |
226 | 0 | } |
227 | | |
228 | | /////////////////////////////// |
229 | | // Search for biased BGR values |
230 | | // ---------------------------- |
231 | | |
232 | 0 | int NNQuantizer::contest(int b, int g, int r) { |
233 | | // finds closest neuron (min dist) and updates freq |
234 | | // finds best neuron (min dist-bias) and returns position |
235 | | // for frequently chosen neurons, freq[i] is high and bias[i] is negative |
236 | | // bias[i] = gamma*((1/netsize)-freq[i]) |
237 | |
|
238 | 0 | int i,dist,a,biasdist,betafreq; |
239 | 0 | int bestpos,bestbiaspos,bestd,bestbiasd; |
240 | 0 | int *p,*f, *n; |
241 | |
|
242 | 0 | bestd = ~(((int) 1)<<31); |
243 | 0 | bestbiasd = bestd; |
244 | 0 | bestpos = -1; |
245 | 0 | bestbiaspos = bestpos; |
246 | 0 | p = bias; |
247 | 0 | f = freq; |
248 | |
|
249 | 0 | for (i = 0; i < netsize; i++) { |
250 | 0 | n = network[i]; |
251 | 0 | dist = n[FI_RGBA_BLUE] - b; |
252 | 0 | if (dist < 0) |
253 | 0 | dist = -dist; |
254 | 0 | a = n[FI_RGBA_GREEN] - g; |
255 | 0 | if (a < 0) |
256 | 0 | a = -a; |
257 | 0 | dist += a; |
258 | 0 | a = n[FI_RGBA_RED] - r; |
259 | 0 | if (a < 0) |
260 | 0 | a = -a; |
261 | 0 | dist += a; |
262 | 0 | if (dist < bestd) { |
263 | 0 | bestd = dist; |
264 | 0 | bestpos = i; |
265 | 0 | } |
266 | 0 | biasdist = dist - ((*p)>>(intbiasshift-netbiasshift)); |
267 | 0 | if (biasdist < bestbiasd) { |
268 | 0 | bestbiasd = biasdist; |
269 | 0 | bestbiaspos = i; |
270 | 0 | } |
271 | 0 | betafreq = (*f >> betashift); |
272 | 0 | *f++ -= betafreq; |
273 | 0 | *p++ += (betafreq << gammashift); |
274 | 0 | } |
275 | 0 | freq[bestpos] += beta; |
276 | 0 | bias[bestpos] -= betagamma; |
277 | 0 | return bestbiaspos; |
278 | 0 | } |
279 | | |
280 | | /////////////////////////////////////////////////////// |
281 | | // Move neuron i towards biased (b,g,r) by factor alpha |
282 | | // ---------------------------------------------------- |
283 | | |
284 | 0 | void NNQuantizer::altersingle(int alpha, int i, int b, int g, int r) { |
285 | 0 | int *n; |
286 | |
|
287 | 0 | n = network[i]; // alter hit neuron |
288 | 0 | n[FI_RGBA_BLUE] -= (alpha * (n[FI_RGBA_BLUE] - b)) / initalpha; |
289 | 0 | n[FI_RGBA_GREEN] -= (alpha * (n[FI_RGBA_GREEN] - g)) / initalpha; |
290 | 0 | n[FI_RGBA_RED] -= (alpha * (n[FI_RGBA_RED] - r)) / initalpha; |
291 | 0 | } |
292 | | |
293 | | //////////////////////////////////////////////////////////////////////////////////// |
294 | | // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] |
295 | | // --------------------------------------------------------------------------------- |
296 | | |
297 | 0 | void NNQuantizer::alterneigh(int rad, int i, int b, int g, int r) { |
298 | 0 | int j, k, lo, hi, a; |
299 | 0 | int *p, *q; |
300 | |
|
301 | 0 | lo = i - rad; if (lo < -1) lo = -1; |
302 | 0 | hi = i + rad; if (hi > netsize) hi = netsize; |
303 | |
|
304 | 0 | j = i+1; |
305 | 0 | k = i-1; |
306 | 0 | q = radpower; |
307 | 0 | while ((j < hi) || (k > lo)) { |
308 | 0 | a = (*(++q)); |
309 | 0 | if (j < hi) { |
310 | 0 | p = network[j]; |
311 | 0 | p[FI_RGBA_BLUE] -= (a * (p[FI_RGBA_BLUE] - b)) / alpharadbias; |
312 | 0 | p[FI_RGBA_GREEN] -= (a * (p[FI_RGBA_GREEN] - g)) / alpharadbias; |
313 | 0 | p[FI_RGBA_RED] -= (a * (p[FI_RGBA_RED] - r)) / alpharadbias; |
314 | 0 | j++; |
315 | 0 | } |
316 | 0 | if (k > lo) { |
317 | 0 | p = network[k]; |
318 | 0 | p[FI_RGBA_BLUE] -= (a * (p[FI_RGBA_BLUE] - b)) / alpharadbias; |
319 | 0 | p[FI_RGBA_GREEN] -= (a * (p[FI_RGBA_GREEN] - g)) / alpharadbias; |
320 | 0 | p[FI_RGBA_RED] -= (a * (p[FI_RGBA_RED] - r)) / alpharadbias; |
321 | 0 | k--; |
322 | 0 | } |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | ///////////////////// |
327 | | // Main Learning Loop |
328 | | // ------------------ |
329 | | |
330 | | /** |
331 | | Get a pixel sample at position pos. Handle 4-byte boundary alignment. |
332 | | @param pos pixel position in a WxHx3 pixel buffer |
333 | | @param b blue pixel component |
334 | | @param g green pixel component |
335 | | @param r red pixel component |
336 | | */ |
337 | 0 | void NNQuantizer::getSample(long pos, int *b, int *g, int *r) { |
338 | | // get equivalent pixel coordinates |
339 | | // - assume it's a 24-bit image - |
340 | 0 | int x = pos % img_line; |
341 | 0 | int y = pos / img_line; |
342 | |
|
343 | 0 | BYTE *bits = FreeImage_GetScanLine(dib_ptr, y) + x; |
344 | |
|
345 | 0 | *b = bits[FI_RGBA_BLUE] << netbiasshift; |
346 | 0 | *g = bits[FI_RGBA_GREEN] << netbiasshift; |
347 | 0 | *r = bits[FI_RGBA_RED] << netbiasshift; |
348 | 0 | } |
349 | | |
350 | 0 | void NNQuantizer::learn(int sampling_factor) { |
351 | 0 | int i, j, b, g, r; |
352 | 0 | int radius, rad, alpha, step, delta, samplepixels; |
353 | 0 | int alphadec; // biased by 10 bits |
354 | 0 | long pos, lengthcount; |
355 | | |
356 | | // image size as viewed by the scan algorithm |
357 | 0 | lengthcount = img_width * img_height * 3; |
358 | | |
359 | | // number of samples used for the learning phase |
360 | 0 | samplepixels = lengthcount / (3 * sampling_factor); |
361 | | |
362 | | // decrease learning rate after delta pixel presentations |
363 | 0 | delta = samplepixels / ncycles; |
364 | 0 | if(delta == 0) { |
365 | | // avoid a 'divide by zero' error with very small images |
366 | 0 | delta = 1; |
367 | 0 | } |
368 | | |
369 | | // initialize learning parameters |
370 | 0 | alphadec = 30 + ((sampling_factor - 1) / 3); |
371 | 0 | alpha = initalpha; |
372 | 0 | radius = initradius; |
373 | | |
374 | 0 | rad = radius >> radiusbiasshift; |
375 | 0 | if (rad <= 1) rad = 0; |
376 | 0 | for (i = 0; i < rad; i++) |
377 | 0 | radpower[i] = alpha*(((rad*rad - i*i)*radbias)/(rad*rad)); |
378 | | |
379 | | // initialize pseudo-random scan |
380 | 0 | if ((lengthcount % prime1) != 0) |
381 | 0 | step = 3*prime1; |
382 | 0 | else { |
383 | 0 | if ((lengthcount % prime2) != 0) |
384 | 0 | step = 3*prime2; |
385 | 0 | else { |
386 | 0 | if ((lengthcount % prime3) != 0) |
387 | 0 | step = 3*prime3; |
388 | 0 | else |
389 | 0 | step = 3*prime4; |
390 | 0 | } |
391 | 0 | } |
392 | | |
393 | 0 | i = 0; // iteration |
394 | 0 | pos = 0; // pixel position |
395 | |
|
396 | 0 | while (i < samplepixels) { |
397 | | // get next learning sample |
398 | 0 | getSample(pos, &b, &g, &r); |
399 | | |
400 | | // find winning neuron |
401 | 0 | j = contest(b, g, r); |
402 | | |
403 | | // alter winner |
404 | 0 | altersingle(alpha, j, b, g, r); |
405 | | |
406 | | // alter neighbours |
407 | 0 | if (rad) alterneigh(rad, j, b, g, r); |
408 | | |
409 | | // next sample |
410 | 0 | pos += step; |
411 | 0 | while (pos >= lengthcount) pos -= lengthcount; |
412 | | |
413 | 0 | i++; |
414 | 0 | if (i % delta == 0) { |
415 | | // decrease learning rate and also the neighborhood |
416 | 0 | alpha -= alpha / alphadec; |
417 | 0 | radius -= radius / radiusdec; |
418 | 0 | rad = radius >> radiusbiasshift; |
419 | 0 | if (rad <= 1) rad = 0; |
420 | 0 | for (j = 0; j < rad; j++) |
421 | 0 | radpower[j] = alpha * (((rad*rad - j*j) * radbias) / (rad*rad)); |
422 | 0 | } |
423 | 0 | } |
424 | | |
425 | 0 | } |
426 | | |
427 | | ////////////// |
428 | | // Quantizer |
429 | | // ----------- |
430 | | |
431 | 0 | FIBITMAP* NNQuantizer::Quantize(FIBITMAP *dib, int ReserveSize, RGBQUAD *ReservePalette, int sampling) { |
432 | |
|
433 | 0 | if ((!dib) || (FreeImage_GetBPP(dib) != 24)) { |
434 | 0 | return NULL; |
435 | 0 | } |
436 | | |
437 | | // 1) Select a sampling factor in range 1..30 (input parameter 'sampling') |
438 | | // 1 => slower, 30 => faster. Default value is 1 |
439 | | |
440 | | |
441 | | // 2) Get DIB parameters |
442 | | |
443 | 0 | dib_ptr = dib; |
444 | | |
445 | 0 | img_width = FreeImage_GetWidth(dib); // DIB width |
446 | 0 | img_height = FreeImage_GetHeight(dib); // DIB height |
447 | 0 | img_line = FreeImage_GetLine(dib); // DIB line length in bytes (should be equal to 3 x W) |
448 | | |
449 | | // For small images, adjust the sampling factor to avoid a 'divide by zero' error later |
450 | | // (see delta in learn() routine) |
451 | 0 | int adjust = (img_width * img_height) / ncycles; |
452 | 0 | if(sampling >= adjust) |
453 | 0 | sampling = 1; |
454 | | |
455 | | |
456 | | // 3) Initialize the network and apply the learning algorithm |
457 | |
|
458 | 0 | if( netsize > ReserveSize ) { |
459 | 0 | netsize -= ReserveSize; |
460 | 0 | initnet(); |
461 | 0 | learn(sampling); |
462 | 0 | unbiasnet(); |
463 | 0 | netsize += ReserveSize; |
464 | 0 | } |
465 | | |
466 | | // 3.5) Overwrite the last few palette entries with the reserved ones |
467 | 0 | for (int i = 0; i < ReserveSize; i++) { |
468 | 0 | network[netsize - ReserveSize + i][FI_RGBA_BLUE] = ReservePalette[i].rgbBlue; |
469 | 0 | network[netsize - ReserveSize + i][FI_RGBA_GREEN] = ReservePalette[i].rgbGreen; |
470 | 0 | network[netsize - ReserveSize + i][FI_RGBA_RED] = ReservePalette[i].rgbRed; |
471 | 0 | network[netsize - ReserveSize + i][3] = netsize - ReserveSize + i; |
472 | 0 | } |
473 | | |
474 | | // 4) Allocate a new 8-bit DIB |
475 | |
|
476 | 0 | FIBITMAP *new_dib = FreeImage_Allocate(img_width, img_height, 8); |
477 | |
|
478 | 0 | if (new_dib == NULL) |
479 | 0 | return NULL; |
480 | | |
481 | | // 5) Write the quantized palette |
482 | | |
483 | 0 | RGBQUAD *new_pal = FreeImage_GetPalette(new_dib); |
484 | |
|
485 | 0 | for (int j = 0; j < netsize; j++) { |
486 | 0 | new_pal[j].rgbBlue = (BYTE)network[j][FI_RGBA_BLUE]; |
487 | 0 | new_pal[j].rgbGreen = (BYTE)network[j][FI_RGBA_GREEN]; |
488 | 0 | new_pal[j].rgbRed = (BYTE)network[j][FI_RGBA_RED]; |
489 | 0 | } |
490 | |
|
491 | 0 | inxbuild(); |
492 | | |
493 | | // 6) Write output image using inxsearch(b,g,r) |
494 | |
|
495 | 0 | for (WORD rows = 0; rows < img_height; rows++) { |
496 | 0 | BYTE *new_bits = FreeImage_GetScanLine(new_dib, rows); |
497 | 0 | BYTE *bits = FreeImage_GetScanLine(dib_ptr, rows); |
498 | |
|
499 | 0 | for (WORD cols = 0; cols < img_width; cols++) { |
500 | 0 | new_bits[cols] = (BYTE)inxsearch(bits[FI_RGBA_BLUE], bits[FI_RGBA_GREEN], bits[FI_RGBA_RED]); |
501 | |
|
502 | 0 | bits += 3; |
503 | 0 | } |
504 | 0 | } |
505 | |
|
506 | 0 | return (FIBITMAP*) new_dib; |
507 | 0 | } |