1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 -
4 - Redistribution and use in source and binary forms, with or without
5 - modification, are permitted provided that the following conditions
6 - are met:
7 - 1. Redistributions of source code must retain the above copyright
8 - notice, this list of conditions and the following disclaimer.
9 - 2. Redistributions in binary form must reproduce the above
10 - copyright notice, this list of conditions and the following
11 - disclaimer in the documentation and/or other materials
12 - provided with the distribution.
13 -
14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *====================================================================*/
26
27 /*!
28 * \file coloring.c
29 * <pre>
30 *
31 * Coloring "gray" pixels
32 * PIX *pixColorGrayRegions()
33 * l_int32 pixColorGray()
34 * PIX *pixColorGrayMasked()
35 *
36 * Adjusting one or more colors to a target color
37 * PIX *pixSnapColor()
38 * PIX *pixSnapColorCmap()
39 *
40 * Piecewise linear color mapping based on a source/target pair
41 * PIX *pixLinearMapToTargetColor()
42 * l_int32 pixelLinearMapToTargetColor()
43 *
44 * Fractional shift of RGB towards black or white
45 * PIX *pixShiftByComponent()
46 * l_int32 pixelShiftByComponent()
47 * l_int32 pixelFractionalShift()
48 *
49 * There are several "coloring" functions in leptonica.
50 * You can find them in these files:
51 * coloring.c
52 * paintcmap.c
53 * pix2.c
54 * blend.c
55 * enhance.c
56 *
57 * They fall into the following categories:
58 *
59 * (1) Moving either the light or dark pixels toward a
60 * specified color. (pixColorGray, pixColorGrayMasked)
61 * (2) Forcing all pixels whose color is within some delta of a
62 * specified color to move to that color. (pixSnapColor)
63 * (3) Doing a piecewise linear color shift specified by a source
64 * and a target color. Each component shifts independently.
65 * (pixLinearMapToTargetColor)
66 * (4) Shifting all colors by a given fraction of their distance
67 * from 0 (if shifting down) or from 255 (if shifting up).
68 * This is useful for colorizing either the background or
69 * the foreground of a grayscale image. (pixShiftByComponent)
70 * (5) Shifting all colors by a component-dependent fraction of
71 * their distance from 0 (if shifting down) or from 255 (if
72 * shifting up). This is useful for modifying the color to
73 * compensate for color shifts in acquisition or printing.
74 * (enhance.c: pixColorShiftRGB, pixMosaicColorShiftRGB).
75 * (6) Repainting selected pixels. (paintcmap.c: pixSetSelectMaskedCmap)
76 * (7) Blending a fraction of a specific color with the existing RGB
77 * color. (pix2.c: pixBlendInRect())
78 * (8) Changing selected colors in a colormap.
79 * (paintcmap.c: pixSetSelectCmap, pixSetSelectMaskedCmap)
80 * (9) Shifting all the pixels towards black or white depending on
81 * the gray value of a second image. (blend.c: pixFadeWithGray)
82 * (10) Changing the hue, saturation or brightness, by changing the
83 * appropriate parameter in HSV color space by a fraction of
84 * the distance toward its end-point. For example, you can change
85 * the brightness by moving each pixel's v-parameter a specified
86 * fraction of the distance toward 0 (darkening) or toward 255
87 * (brightening). (enhance.c: pixModifySaturation,
88 * pixModifyHue, pixModifyBrightness)
89 * </pre>
90 */
91
92 #include "allheaders.h"
93
94
95 /*---------------------------------------------------------------------*
96 * Coloring "gray" pixels *
97 *---------------------------------------------------------------------*/
98 /*!
99 * \brief pixColorGrayRegions()
100 *
101 * \param[in] pixs 2, 4 or 8 bpp gray, rgb, or colormapped
102 * \param[in] boxa of regions in which to apply color
103 * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
104 * \param[in] thresh average value below/above which pixel is unchanged
105 * \param[in] rval, gval, bval new color to paint
106 * \return pixd, or NULL on error
107 *
108 * <pre>
109 * Notes:
110 * (1) This generates a new image, where some of the pixels in each
111 * box in the boxa are colorized. See pixColorGray() for usage
112 * with %type and %thresh. Note that %thresh is only used for
113 * rgb; it is ignored for colormapped images.
114 * (2) If the input image is colormapped, the new image will be 8 bpp
115 * colormapped if possible; otherwise, it will be converted
116 * to 32 bpp rgb. Only pixels that are strictly gray will be
117 * colorized.
118 * (3) If the input image is not colormapped, it is converted to rgb.
119 * A "gray" value for a pixel is determined by averaging the
120 * components, and the output rgb value is determined from this.
121 * (4) This can be used in conjunction with pixHasHighlightRed() to
122 * add highlight color to a grayscale image.
123 * </pre>
124 */
125 PIX *
pixColorGrayRegions(PIX * pixs,BOXA * boxa,l_int32 type,l_int32 thresh,l_int32 rval,l_int32 gval,l_int32 bval)126 pixColorGrayRegions(PIX *pixs,
127 BOXA *boxa,
128 l_int32 type,
129 l_int32 thresh,
130 l_int32 rval,
131 l_int32 gval,
132 l_int32 bval)
133 {
134 l_int32 i, n, ncolors, ngray;
135 BOX *box;
136 PIX *pixd;
137 PIXCMAP *cmap;
138
139 PROCNAME("pixColorGrayRegions");
140
141 if (!pixs || pixGetDepth(pixs) == 1)
142 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
143 if (!boxa)
144 return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
145 if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
146 return (PIX *)ERROR_PTR("invalid type", procName, NULL);
147
148 /* If cmapped and there is room in an 8 bpp colormap for
149 * expansion, convert pixs to 8 bpp, and colorize. */
150 cmap = pixGetColormap(pixs);
151 if (cmap) {
152 ncolors = pixcmapGetCount(cmap);
153 pixcmapCountGrayColors(cmap, &ngray);
154 if (ncolors + ngray < 255) {
155 pixd = pixConvertTo8(pixs, 1); /* always new image */
156 pixColorGrayRegionsCmap(pixd, boxa, type, rval, gval, bval);
157 return pixd;
158 }
159 }
160
161 /* The output will be rgb. Make sure the thresholds are valid */
162 if (type == L_PAINT_LIGHT) { /* thresh should be low */
163 if (thresh >= 255)
164 return (PIX *)ERROR_PTR("thresh must be < 255", procName, NULL);
165 if (thresh > 127)
166 L_WARNING("threshold set very high\n", procName);
167 } else { /* type == L_PAINT_DARK; thresh should be high */
168 if (thresh <= 0)
169 return (PIX *)ERROR_PTR("thresh must be > 0", procName, NULL);
170 if (thresh < 128)
171 L_WARNING("threshold set very low\n", procName);
172 }
173
174 pixd = pixConvertTo32(pixs); /* always new image */
175 n = boxaGetCount(boxa);
176 for (i = 0; i < n; i++) {
177 box = boxaGetBox(boxa, i, L_CLONE);
178 pixColorGray(pixd, box, type, thresh, rval, gval, bval);
179 boxDestroy(&box);
180 }
181
182 return pixd;
183 }
184
185
186 /*!
187 * \brief pixColorGray()
188 *
189 * \param[in] pixs 8 bpp gray, rgb or colormapped image
190 * \param[in] box [optional] region in which to apply color; can be NULL
191 * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
192 * \param[in] thresh average value below/above which pixel is unchanged
193 * \param[in] rval, gval, bval new color to paint
194 * \return 0 if OK; 1 on error
195 *
196 * <pre>
197 * Notes:
198 * (1) This is an in-place operation; pixs is modified.
199 * If pixs is colormapped, the operation will add colors to the
200 * colormap. Otherwise, pixs will be converted to 32 bpp rgb if
201 * it is initially 8 bpp gray.
202 * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
203 * preserving antialiasing.
204 * If type == L_PAINT_DARK, it colorizes non-white pixels,
205 * preserving antialiasing.
206 * (3) If box is NULL, applies function to the entire image; otherwise,
207 * clips the operation to the intersection of the box and pix.
208 * (4) If colormapped, calls pixColorGrayCmap(), which applies the
209 * coloring algorithm only to pixels that are strictly gray.
210 * (5) For RGB, determines a "gray" value by averaging; then uses this
211 * value, plus the input rgb target, to generate the output
212 * pixel values.
213 * (6) thresh is only used for rgb; it is ignored for colormapped pix.
214 * If type == L_PAINT_LIGHT, use thresh = 0 if all pixels are to
215 * be colored (black pixels will be unaltered).
216 * In situations where there are a lot of black pixels,
217 * setting thresh > 0 will make the function considerably
218 * more efficient without affecting the final result.
219 * If type == L_PAINT_DARK, use thresh = 255 if all pixels
220 * are to be colored (white pixels will be unaltered).
221 * In situations where there are a lot of white pixels,
222 * setting thresh < 255 will make the function considerably
223 * more efficient without affecting the final result.
224 * </pre>
225 */
226 l_int32
pixColorGray(PIX * pixs,BOX * box,l_int32 type,l_int32 thresh,l_int32 rval,l_int32 gval,l_int32 bval)227 pixColorGray(PIX *pixs,
228 BOX *box,
229 l_int32 type,
230 l_int32 thresh,
231 l_int32 rval,
232 l_int32 gval,
233 l_int32 bval)
234 {
235 l_int32 i, j, w, h, d, wpl, x1, x2, y1, y2, bw, bh;
236 l_int32 nrval, ngval, nbval, aveval;
237 l_float32 factor;
238 l_uint32 val32;
239 l_uint32 *line, *data;
240 PIX *pixt;
241 PIXCMAP *cmap;
242
243 PROCNAME("pixColorGray");
244
245 if (!pixs)
246 return ERROR_INT("pixs not defined", procName, 1);
247 if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
248 return ERROR_INT("invalid type", procName, 1);
249
250 cmap = pixGetColormap(pixs);
251 pixGetDimensions(pixs, &w, &h, &d);
252 if (!cmap && d != 8 && d != 32)
253 return ERROR_INT("pixs not cmapped, 8 bpp or rgb", procName, 1);
254 if (cmap)
255 return pixColorGrayCmap(pixs, box, type, rval, gval, bval);
256
257 /* rgb or 8 bpp gray image; check the thresh */
258 if (type == L_PAINT_LIGHT) { /* thresh should be low */
259 if (thresh >= 255)
260 return ERROR_INT("thresh must be < 255; else this is a no-op",
261 procName, 1);
262 if (thresh > 127)
263 L_WARNING("threshold set very high\n", procName);
264 } else { /* type == L_PAINT_DARK; thresh should be high */
265 if (thresh <= 0)
266 return ERROR_INT("thresh must be > 0; else this is a no-op",
267 procName, 1);
268 if (thresh < 128)
269 L_WARNING("threshold set very low\n", procName);
270 }
271
272 /* In-place conversion to 32 bpp if necessary */
273 if (d == 8) {
274 pixt = pixConvertTo32(pixs);
275 pixTransferAllData(pixs, &pixt, 1, 0);
276 }
277
278 if (!box) {
279 x1 = y1 = 0;
280 x2 = w;
281 y2 = h;
282 } else {
283 boxGetGeometry(box, &x1, &y1, &bw, &bh);
284 x2 = x1 + bw - 1;
285 y2 = y1 + bh - 1;
286 }
287
288 data = pixGetData(pixs);
289 wpl = pixGetWpl(pixs);
290 factor = 1. / 255.;
291 for (i = y1; i <= y2; i++) {
292 if (i < 0 || i >= h)
293 continue;
294 line = data + i * wpl;
295 for (j = x1; j <= x2; j++) {
296 if (j < 0 || j >= w)
297 continue;
298 val32 = *(line + j);
299 aveval = ((val32 >> 24) + ((val32 >> 16) & 0xff) +
300 ((val32 >> 8) & 0xff)) / 3;
301 if (type == L_PAINT_LIGHT) {
302 if (aveval < thresh) /* skip sufficiently dark pixels */
303 continue;
304 nrval = (l_int32)(rval * aveval * factor);
305 ngval = (l_int32)(gval * aveval * factor);
306 nbval = (l_int32)(bval * aveval * factor);
307 } else { /* type == L_PAINT_DARK */
308 if (aveval > thresh) /* skip sufficiently light pixels */
309 continue;
310 nrval = rval + (l_int32)((255. - rval) * aveval * factor);
311 ngval = gval + (l_int32)((255. - gval) * aveval * factor);
312 nbval = bval + (l_int32)((255. - bval) * aveval * factor);
313 }
314 composeRGBPixel(nrval, ngval, nbval, &val32);
315 *(line + j) = val32;
316 }
317 }
318
319 return 0;
320 }
321
322
323 /*!
324 * \brief pixColorGrayMasked()
325 *
326 * \param[in] pixs 8 bpp gray, rgb or colormapped image
327 * \param[in] pixm 1 bpp mask, through which to apply color
328 * \param[in] type L_PAINT_LIGHT, L_PAINT_DARK
329 * \param[in] thresh average value below/above which pixel is unchanged
330 * \param[in] rval, gval, bval new color to paint
331 * \return pixd colorized, or NULL on error
332 *
333 * <pre>
334 * Notes:
335 * (1) This generates a new image, where some of the pixels under
336 * FG in the mask are colorized.
337 * (2) See pixColorGray() for usage with %type and %thresh. Note
338 * that %thresh is only used for rgb; it is ignored for
339 * colormapped images. In most cases, the mask will be over
340 * the darker parts and %type == L_PAINT_DARK.
341 * (3) If pixs is colormapped this calls pixColorMaskedCmap(),
342 * which adds colors to the colormap for pixd; it only adds
343 * colors corresponding to strictly gray colors in the colormap.
344 * Otherwise, if pixs is 8 bpp gray, pixd will be 32 bpp rgb.
345 * (4) If pixs is 32 bpp rgb, for each pixel a "gray" value is
346 * found by averaging. This average is then used with the
347 * input rgb target to generate the output pixel values.
348 * (5) This can be used in conjunction with pixHasHighlightRed() to
349 * add highlight color to a grayscale image.
350 * </pre>
351 */
352 PIX *
pixColorGrayMasked(PIX * pixs,PIX * pixm,l_int32 type,l_int32 thresh,l_int32 rval,l_int32 gval,l_int32 bval)353 pixColorGrayMasked(PIX *pixs,
354 PIX *pixm,
355 l_int32 type,
356 l_int32 thresh,
357 l_int32 rval,
358 l_int32 gval,
359 l_int32 bval)
360 {
361 l_int32 i, j, w, h, d, wm, hm, wmin, hmin, wpl, wplm;
362 l_int32 nrval, ngval, nbval, aveval;
363 l_float32 factor;
364 l_uint32 val32;
365 l_uint32 *line, *data, *linem, *datam;
366 PIX *pixd;
367 PIXCMAP *cmap;
368
369 PROCNAME("pixColorGrayMasked");
370
371 if (!pixs)
372 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
373 if (!pixm || pixGetDepth(pixm) != 1)
374 return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
375 if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
376 return (PIX *)ERROR_PTR("invalid type", procName, NULL);
377
378 cmap = pixGetColormap(pixs);
379 pixGetDimensions(pixs, &w, &h, &d);
380 if (!cmap && d != 8 && d != 32)
381 return (PIX *)ERROR_PTR("pixs not cmapped, 8 bpp gray or 32 bpp",
382 procName, NULL);
383 if (cmap) {
384 pixd = pixCopy(NULL, pixs);
385 pixColorGrayMaskedCmap(pixd, pixm, type, rval, gval, bval);
386 return pixd;
387 }
388
389 /* rgb or 8 bpp gray image; check the thresh */
390 if (type == L_PAINT_LIGHT) { /* thresh should be low */
391 if (thresh >= 255)
392 return (PIX *)ERROR_PTR(
393 "thresh must be < 255; else this is a no-op", procName, NULL);
394 if (thresh > 127)
395 L_WARNING("threshold set very high\n", procName);
396 } else { /* type == L_PAINT_DARK; thresh should be high */
397 if (thresh <= 0)
398 return (PIX *)ERROR_PTR(
399 "thresh must be > 0; else this is a no-op", procName, NULL);
400 if (thresh < 128)
401 L_WARNING("threshold set very low\n", procName);
402 }
403
404 pixGetDimensions(pixm, &wm, &hm, NULL);
405 if (wm != w)
406 L_WARNING("wm = %d differs from w = %d\n", procName, wm, w);
407 if (hm != h)
408 L_WARNING("hm = %d differs from h = %d\n", procName, hm, h);
409 wmin = L_MIN(w, wm);
410 hmin = L_MIN(h, hm);
411 if (d == 8)
412 pixd = pixConvertTo32(pixs);
413 else
414 pixd = pixCopy(NULL, pixs);
415
416 data = pixGetData(pixd);
417 wpl = pixGetWpl(pixd);
418 datam = pixGetData(pixm);
419 wplm = pixGetWpl(pixm);
420 factor = 1. / 255.;
421 for (i = 0; i < hmin; i++) {
422 line = data + i * wpl;
423 linem = datam + i * wplm;
424 for (j = 0; j < wmin; j++) {
425 if (GET_DATA_BIT(linem, j) == 0)
426 continue;
427 val32 = *(line + j);
428 aveval = ((val32 >> 24) + ((val32 >> 16) & 0xff) +
429 ((val32 >> 8) & 0xff)) / 3;
430 if (type == L_PAINT_LIGHT) {
431 if (aveval < thresh) /* skip sufficiently dark pixels */
432 continue;
433 nrval = (l_int32)(rval * aveval * factor);
434 ngval = (l_int32)(gval * aveval * factor);
435 nbval = (l_int32)(bval * aveval * factor);
436 } else { /* type == L_PAINT_DARK */
437 if (aveval > thresh) /* skip sufficiently light pixels */
438 continue;
439 nrval = rval + (l_int32)((255. - rval) * aveval * factor);
440 ngval = gval + (l_int32)((255. - gval) * aveval * factor);
441 nbval = bval + (l_int32)((255. - bval) * aveval * factor);
442 }
443 composeRGBPixel(nrval, ngval, nbval, &val32);
444 *(line + j) = val32;
445 }
446 }
447
448 return pixd;
449 }
450
451
452 /*------------------------------------------------------------------*
453 * Adjusting one or more colors to a target color *
454 *------------------------------------------------------------------*/
455 /*!
456 * \brief pixSnapColor()
457 *
458 * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
459 * \param[in] pixs colormapped or 8 bpp gray or 32 bpp rgb
460 * \param[in] srcval color center to be selected for change: 0xrrggbb00
461 * \param[in] dstval target color for pixels: 0xrrggbb00
462 * \param[in] diff max absolute difference, applied to all components
463 * \return pixd with all pixels within diff of pixval set to pixval,
464 * or pixd on error
465 *
466 * <pre>
467 * Notes:
468 * (1) For inplace operation, call it this way:
469 * pixSnapColor(pixs, pixs, ... )
470 * (2) For generating a new pixd:
471 * pixd = pixSnapColor(NULL, pixs, ...)
472 * (3) If pixs has a colormap, it is handled by pixSnapColorCmap().
473 * (4) All pixels within 'diff' of 'srcval', componentwise,
474 * will be changed to 'dstval'.
475 * </pre>
476 */
477 PIX *
pixSnapColor(PIX * pixd,PIX * pixs,l_uint32 srcval,l_uint32 dstval,l_int32 diff)478 pixSnapColor(PIX *pixd,
479 PIX *pixs,
480 l_uint32 srcval,
481 l_uint32 dstval,
482 l_int32 diff)
483 {
484 l_int32 val, sval, dval;
485 l_int32 rval, gval, bval, rsval, gsval, bsval;
486 l_int32 i, j, w, h, d, wpl;
487 l_uint32 pixel;
488 l_uint32 *line, *data;
489
490 PROCNAME("pixSnapColor");
491
492 if (!pixs)
493 return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
494 if (pixd && (pixd != pixs))
495 return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
496
497 if (pixGetColormap(pixs))
498 return pixSnapColorCmap(pixd, pixs, srcval, dstval, diff);
499
500 /* pixs does not have a colormap; it must be 8 bpp gray or
501 * 32 bpp rgb. */
502 if (pixGetDepth(pixs) < 8)
503 return (PIX *)ERROR_PTR("pixs is < 8 bpp", procName, pixd);
504
505 /* Do the work on pixd */
506 if (!pixd)
507 pixd = pixCopy(NULL, pixs);
508
509 pixGetDimensions(pixd, &w, &h, &d);
510 data = pixGetData(pixd);
511 wpl = pixGetWpl(pixd);
512 if (d == 8) {
513 sval = srcval & 0xff;
514 dval = dstval & 0xff;
515 for (i = 0; i < h; i++) {
516 line = data + i * wpl;
517 for (j = 0; j < w; j++) {
518 val = GET_DATA_BYTE(line, j);
519 if (L_ABS(val - sval) <= diff)
520 SET_DATA_BYTE(line, j, dval);
521 }
522 }
523 } else { /* d == 32 */
524 extractRGBValues(srcval, &rsval, &gsval, &bsval);
525 for (i = 0; i < h; i++) {
526 line = data + i * wpl;
527 for (j = 0; j < w; j++) {
528 pixel = *(line + j);
529 extractRGBValues(pixel, &rval, &gval, &bval);
530 if ((L_ABS(rval - rsval) <= diff) &&
531 (L_ABS(gval - gsval) <= diff) &&
532 (L_ABS(bval - bsval) <= diff))
533 *(line + j) = dstval; /* replace */
534 }
535 }
536 }
537
538 return pixd;
539 }
540
541
542 /*!
543 * \brief pixSnapColorCmap()
544 *
545 * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
546 * \param[in] pixs colormapped
547 * \param[in] srcval color center to be selected for change: 0xrrggbb00
548 * \param[in] dstval target color for pixels: 0xrrggbb00
549 * \param[in] diff max absolute difference, applied to all components
550 * \return pixd with all pixels within diff of srcval set to dstval,
551 * or pixd on error
552 *
553 * <pre>
554 * Notes:
555 * (1) For inplace operation, call it this way:
556 * pixSnapCcmap(pixs, pixs, ... )
557 * (2) For generating a new pixd:
558 * pixd = pixSnapCmap(NULL, pixs, ...)
559 * (3) pixs must have a colormap.
560 * (4) All colors within 'diff' of 'srcval', componentwise,
561 * will be changed to 'dstval'.
562 * </pre>
563 */
564 PIX *
pixSnapColorCmap(PIX * pixd,PIX * pixs,l_uint32 srcval,l_uint32 dstval,l_int32 diff)565 pixSnapColorCmap(PIX *pixd,
566 PIX *pixs,
567 l_uint32 srcval,
568 l_uint32 dstval,
569 l_int32 diff)
570 {
571 l_int32 i, ncolors, index, found;
572 l_int32 rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
573 l_int32 *tab;
574 PIX *pixm;
575 PIXCMAP *cmap;
576
577 PROCNAME("pixSnapColorCmap");
578
579 if (!pixs)
580 return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
581 if (!pixGetColormap(pixs))
582 return (PIX *)ERROR_PTR("cmap not found", procName, pixd);
583 if (pixd && (pixd != pixs))
584 return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
585
586 if (!pixd)
587 pixd = pixCopy(NULL, pixs);
588
589 /* If no free colors, look for one close to the target
590 * that can be commandeered. */
591 cmap = pixGetColormap(pixd);
592 ncolors = pixcmapGetCount(cmap);
593 extractRGBValues(srcval, &rsval, &gsval, &bsval);
594 extractRGBValues(dstval, &rdval, &gdval, &bdval);
595 found = FALSE;
596 if (pixcmapGetFreeCount(cmap) == 0) {
597 for (i = 0; i < ncolors; i++) {
598 pixcmapGetColor(cmap, i, &rval, &gval, &bval);
599 if ((L_ABS(rval - rsval) <= diff) &&
600 (L_ABS(gval - gsval) <= diff) &&
601 (L_ABS(bval - bsval) <= diff)) {
602 index = i;
603 pixcmapResetColor(cmap, index, rdval, gdval, bdval);
604 found = TRUE;
605 break;
606 }
607 }
608 } else { /* just add the new color */
609 pixcmapAddColor(cmap, rdval, gdval, bdval);
610 ncolors = pixcmapGetCount(cmap);
611 index = ncolors - 1; /* index of new destination color */
612 found = TRUE;
613 }
614
615 if (!found) {
616 L_INFO("nothing to do\n", procName);
617 return pixd;
618 }
619
620 /* For each color in cmap that is close enough to srcval,
621 * set the tab value to 1. Then generate a 1 bpp mask with
622 * fg pixels for every pixel in pixd that is close enough
623 * to srcval (i.e., has value 1 in tab). */
624 if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
625 return (PIX *)ERROR_PTR("tab not made", procName, pixd);
626 for (i = 0; i < ncolors; i++) {
627 pixcmapGetColor(cmap, i, &rval, &gval, &bval);
628 if ((L_ABS(rval - rsval) <= diff) &&
629 (L_ABS(gval - gsval) <= diff) &&
630 (L_ABS(bval - bsval) <= diff))
631 tab[i] = 1;
632 }
633 pixm = pixMakeMaskFromLUT(pixd, tab);
634 LEPT_FREE(tab);
635
636 /* Use the binary mask to set all selected pixels to
637 * the dest color index. */
638 pixSetMasked(pixd, pixm, dstval);
639 pixDestroy(&pixm);
640
641 /* Remove all unused colors from the colormap. */
642 pixRemoveUnusedColors(pixd);
643
644 return pixd;
645 }
646
647
648 /*---------------------------------------------------------------------*
649 * Piecewise linear color mapping based on a source/target pair *
650 *---------------------------------------------------------------------*/
651 /*!
652 * \brief pixLinearMapToTargetColor()
653 *
654 * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
655 * \param[in] pixs 32 bpp rgb
656 * \param[in] srcval source color: 0xrrggbb00
657 * \param[in] dstval target color: 0xrrggbb00
658 * \return pixd with all pixels mapped based on the srcval/destval
659 * mapping, or pixd on error
660 *
661 * <pre>
662 * Notes:
663 * (1) For each component (r, b, g) separately, this does a piecewise
664 * linear mapping of the colors in pixs to colors in pixd.
665 * If rs and rd are the red src and dest components in %srcval and
666 * %dstval, then the range [0 ... rs] in pixs is mapped to
667 * [0 ... rd] in pixd. Likewise, the range [rs ... 255] in pixs
668 * is mapped to [rd ... 255] in pixd. And similarly for green
669 * and blue.
670 * (2) The mapping will in general change the hue of the pixels.
671 * However, if the src and dst targets are related by
672 * a transformation given by pixelFractionalShift(), the hue
673 * is invariant.
674 * (3) For inplace operation, call it this way:
675 * pixLinearMapToTargetColor(pixs, pixs, ... )
676 * (4) For generating a new pixd:
677 * pixd = pixLinearMapToTargetColor(NULL, pixs, ...)
678 * </pre>
679 */
680 PIX *
pixLinearMapToTargetColor(PIX * pixd,PIX * pixs,l_uint32 srcval,l_uint32 dstval)681 pixLinearMapToTargetColor(PIX *pixd,
682 PIX *pixs,
683 l_uint32 srcval,
684 l_uint32 dstval)
685 {
686 l_int32 i, j, w, h, wpl;
687 l_int32 rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
688 l_int32 *rtab, *gtab, *btab;
689 l_uint32 pixel;
690 l_uint32 *line, *data;
691
692 PROCNAME("pixLinearMapToTargetColor");
693
694 if (!pixs)
695 return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
696 if (pixd && (pixd != pixs))
697 return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
698 if (pixGetDepth(pixs) != 32)
699 return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, pixd);
700
701 /* Do the work on pixd */
702 if (!pixd)
703 pixd = pixCopy(NULL, pixs);
704
705 extractRGBValues(srcval, &rsval, &gsval, &bsval);
706 extractRGBValues(dstval, &rdval, &gdval, &bdval);
707 rsval = L_MIN(254, L_MAX(1, rsval));
708 gsval = L_MIN(254, L_MAX(1, gsval));
709 bsval = L_MIN(254, L_MAX(1, bsval));
710 rtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
711 gtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
712 btab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
713 if (!rtab || !gtab || !btab)
714 return (PIX *)ERROR_PTR("calloc fail for tab", procName, pixd);
715 for (i = 0; i < 256; i++) {
716 if (i <= rsval)
717 rtab[i] = (i * rdval) / rsval;
718 else
719 rtab[i] = rdval + ((255 - rdval) * (i - rsval)) / (255 - rsval);
720 if (i <= gsval)
721 gtab[i] = (i * gdval) / gsval;
722 else
723 gtab[i] = gdval + ((255 - gdval) * (i - gsval)) / (255 - gsval);
724 if (i <= bsval)
725 btab[i] = (i * bdval) / bsval;
726 else
727 btab[i] = bdval + ((255 - bdval) * (i - bsval)) / (255 - bsval);
728 }
729 pixGetDimensions(pixd, &w, &h, NULL);
730 data = pixGetData(pixd);
731 wpl = pixGetWpl(pixd);
732 for (i = 0; i < h; i++) {
733 line = data + i * wpl;
734 for (j = 0; j < w; j++) {
735 pixel = line[j];
736 extractRGBValues(pixel, &rval, &gval, &bval);
737 composeRGBPixel(rtab[rval], gtab[gval], btab[bval], &pixel);
738 line[j] = pixel;
739 }
740 }
741
742 LEPT_FREE(rtab);
743 LEPT_FREE(gtab);
744 LEPT_FREE(btab);
745 return pixd;
746 }
747
748
749 /*!
750 * \brief pixelLinearMapToTargetColor()
751 *
752 * \param[in] scolor rgb source color: 0xrrggbb00
753 * \param[in] srcmap source mapping color: 0xrrggbb00
754 * \param[in] dstmap target mapping color: 0xrrggbb00
755 * \param[out] pdcolor rgb dest color: 0xrrggbb00
756 * \return 0 if OK, 1 on error
757 *
758 * <pre>
759 * Notes:
760 * (1) This does this does a piecewise linear mapping of each
761 * component of %scolor to %dcolor, based on the relation
762 * between the components of %srcmap and %dstmap. It is the
763 * same transformation, performed on a single color, as mapped
764 * on every pixel in a pix by pixLinearMapToTargetColor().
765 * (2) For each component, if the sval is larger than the smap,
766 * the dval will be pushed up from dmap towards white.
767 * Otherwise, dval will be pushed down from dmap towards black.
768 * This is because you can visualize the transformation as
769 * a linear stretching where smap moves to dmap, and everything
770 * else follows linearly with 0 and 255 fixed.
771 * (3) The mapping will in general change the hue of %scolor.
772 * However, if the %srcmap and %dstmap targets are related by
773 * a transformation given by pixelFractionalShift(), the hue
774 * will be invariant.
775 * </pre>
776 */
777 l_int32
pixelLinearMapToTargetColor(l_uint32 scolor,l_uint32 srcmap,l_uint32 dstmap,l_uint32 * pdcolor)778 pixelLinearMapToTargetColor(l_uint32 scolor,
779 l_uint32 srcmap,
780 l_uint32 dstmap,
781 l_uint32 *pdcolor)
782 {
783 l_int32 srval, sgval, sbval, drval, dgval, dbval;
784 l_int32 srmap, sgmap, sbmap, drmap, dgmap, dbmap;
785
786 PROCNAME("pixelLinearMapToTargetColor");
787
788 if (!pdcolor)
789 return ERROR_INT("&dcolor not defined", procName, 1);
790 *pdcolor = 0;
791
792 extractRGBValues(scolor, &srval, &sgval, &sbval);
793 extractRGBValues(srcmap, &srmap, &sgmap, &sbmap);
794 extractRGBValues(dstmap, &drmap, &dgmap, &dbmap);
795 srmap = L_MIN(254, L_MAX(1, srmap));
796 sgmap = L_MIN(254, L_MAX(1, sgmap));
797 sbmap = L_MIN(254, L_MAX(1, sbmap));
798
799 if (srval <= srmap)
800 drval = (srval * drmap) / srmap;
801 else
802 drval = drmap + ((255 - drmap) * (srval - srmap)) / (255 - srmap);
803 if (sgval <= sgmap)
804 dgval = (sgval * dgmap) / sgmap;
805 else
806 dgval = dgmap + ((255 - dgmap) * (sgval - sgmap)) / (255 - sgmap);
807 if (sbval <= sbmap)
808 dbval = (sbval * dbmap) / sbmap;
809 else
810 dbval = dbmap + ((255 - dbmap) * (sbval - sbmap)) / (255 - sbmap);
811
812 composeRGBPixel(drval, dgval, dbval, pdcolor);
813 return 0;
814 }
815
816
817 /*------------------------------------------------------------------*
818 * Fractional shift of RGB towards black or white *
819 *------------------------------------------------------------------*/
820 /*!
821 * \brief pixShiftByComponent()
822 *
823 * \param[in] pixd [optional]; either NULL or equal to pixs for in-place
824 * \param[in] pixs 32 bpp rgb
825 * \param[in] srcval source color: 0xrrggbb00
826 * \param[in] dstval target color: 0xrrggbb00
827 * \return pixd with all pixels mapped based on the srcval/destval
828 * mapping, or pixd on error
829 *
830 * <pre>
831 * Notes:
832 * (1) For each component (r, b, g) separately, this does a linear
833 * mapping of the colors in pixs to colors in pixd.
834 * Let rs and rd be the red src and dest components in %srcval and
835 * %dstval, and rval is the red component of the src pixel.
836 * Then for all pixels in pixs, the mapping for the red
837 * component from pixs to pixd is:
838 * if (rd <= rs) (shift toward black)
839 * rval --> (rd/rs) * rval
840 * if (rd > rs) (shift toward white)
841 * (255 - rval) --> ((255 - rs)/(255 - rd)) * (255 - rval)
842 * Thus if rd <= rs, the red component of all pixels is
843 * mapped by the same fraction toward white, and if rd > rs,
844 * they are mapped by the same fraction toward black.
845 * This is essentially a different linear TRC (gamma = 1)
846 * for each component. The source and target color inputs are
847 * just used to generate the three fractions.
848 * (2) Note that this mapping differs from that in
849 * pixLinearMapToTargetColor(), which maps rs --> rd and does
850 * a piecewise stretching in between.
851 * (3) For inplace operation, call it this way:
852 * pixFractionalShiftByComponent(pixs, pixs, ... )
853 * (4) For generating a new pixd:
854 * pixd = pixLinearMapToTargetColor(NULL, pixs, ...)
855 * (5) A simple application is to color a grayscale image.
856 * A light background can be colored using srcval = 0xffffff00
857 * and picking a target background color for dstval.
858 * A dark foreground can be colored by using srcval = 0x0
859 * and choosing a target foreground color for dstval.
860 * </pre>
861 */
862 PIX *
pixShiftByComponent(PIX * pixd,PIX * pixs,l_uint32 srcval,l_uint32 dstval)863 pixShiftByComponent(PIX *pixd,
864 PIX *pixs,
865 l_uint32 srcval,
866 l_uint32 dstval)
867 {
868 l_int32 i, j, w, h, wpl;
869 l_int32 rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
870 l_int32 *rtab, *gtab, *btab;
871 l_uint32 pixel;
872 l_uint32 *line, *data;
873 PIXCMAP *cmap;
874
875 PROCNAME("pixShiftByComponent");
876
877 if (!pixs)
878 return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
879 if (pixd && (pixd != pixs))
880 return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
881 if (pixGetDepth(pixs) != 32 && !pixGetColormap(pixs))
882 return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, pixd);
883
884 /* Do the work on pixd */
885 if (!pixd)
886 pixd = pixCopy(NULL, pixs);
887
888 /* If colormapped, just modify it */
889 if ((cmap = pixGetColormap(pixd)) != NULL) {
890 pixcmapShiftByComponent(cmap, srcval, dstval);
891 return pixd;
892 }
893
894 extractRGBValues(srcval, &rsval, &gsval, &bsval);
895 extractRGBValues(dstval, &rdval, &gdval, &bdval);
896 rtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
897 gtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
898 btab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
899 if (!rtab || !gtab || !btab) {
900 L_ERROR("calloc fail for tab\n", procName);
901 goto cleanup;
902 }
903 for (i = 0; i < 256; i++) {
904 if (rdval == rsval)
905 rtab[i] = i;
906 else if (rdval < rsval)
907 rtab[i] = (i * rdval) / rsval;
908 else
909 rtab[i] = 255 - (255 - rdval) * (255 - i) / (255 - rsval);
910 if (gdval == gsval)
911 gtab[i] = i;
912 else if (gdval < gsval)
913 gtab[i] = (i * gdval) / gsval;
914 else
915 gtab[i] = 255 - (255 - gdval) * (255 - i) / (255 - gsval);
916 if (bdval == bsval)
917 btab[i] = i;
918 else if (bdval < bsval)
919 btab[i] = (i * bdval) / bsval;
920 else
921 btab[i] = 255 - (255 - bdval) * (255 - i) / (255 - bsval);
922 }
923 pixGetDimensions(pixd, &w, &h, NULL);
924 data = pixGetData(pixd);
925 wpl = pixGetWpl(pixd);
926 for (i = 0; i < h; i++) {
927 line = data + i * wpl;
928 for (j = 0; j < w; j++) {
929 pixel = line[j];
930 extractRGBValues(pixel, &rval, &gval, &bval);
931 composeRGBPixel(rtab[rval], gtab[gval], btab[bval], &pixel);
932 line[j] = pixel;
933 }
934 }
935
936 cleanup:
937 LEPT_FREE(rtab);
938 LEPT_FREE(gtab);
939 LEPT_FREE(btab);
940 return pixd;
941 }
942
943
944 /*!
945 * \brief pixelShiftByComponent()
946 *
947 * \param[in] rval, gval, bval
948 * \param[in] srcval source color: 0xrrggbb00
949 * \param[in] dstval target color: 0xrrggbb00
950 * \param[out] ppixel rgb value
951 * \return 0 if OK, 1 on error
952 *
953 * <pre>
954 * Notes:
955 * (1) This is a linear transformation that gives the same result
956 * on a single pixel as pixShiftByComponent() gives
957 * on a pix. Each component is handled separately. If
958 * the dest component is larger than the src, then the
959 * component is pushed toward 255 by the same fraction as
960 * the src --> dest shift.
961 * </pre>
962 */
963 l_int32
pixelShiftByComponent(l_int32 rval,l_int32 gval,l_int32 bval,l_uint32 srcval,l_uint32 dstval,l_uint32 * ppixel)964 pixelShiftByComponent(l_int32 rval,
965 l_int32 gval,
966 l_int32 bval,
967 l_uint32 srcval,
968 l_uint32 dstval,
969 l_uint32 *ppixel)
970 {
971 l_int32 rsval, rdval, gsval, gdval, bsval, bdval, rs, gs, bs;
972
973 PROCNAME("pixelShiftByComponent");
974
975 if (!ppixel)
976 return ERROR_INT("&pixel defined", procName, 1);
977
978 extractRGBValues(srcval, &rsval, &gsval, &bsval);
979 extractRGBValues(dstval, &rdval, &gdval, &bdval);
980 if (rdval == rsval)
981 rs = rval;
982 else if (rdval < rsval)
983 rs = (rval * rdval) / rsval;
984 else
985 rs = 255 - (255 - rdval) * (255 - rval) / (255 - rsval);
986 if (gdval == gsval)
987 gs = gval;
988 else if (gdval < gsval)
989 gs = (gval * gdval) / gsval;
990 else
991 gs = 255 - (255 - gdval) * (255 - gval) / (255 - gsval);
992 if (bdval == bsval)
993 bs = bval;
994 else if (bdval < bsval)
995 bs = (bval * bdval) / bsval;
996 else
997 bs = 255 - (255 - bdval) * (255 - bval) / (255 - bsval);
998 composeRGBPixel(rs, gs, bs, ppixel);
999 return 0;
1000 }
1001
1002
1003 /*!
1004 * \brief pixelFractionalShift()
1005 *
1006 * \param[in] rval, gval, bval
1007 * \param[in] fraction negative toward black; positive toward white
1008 * \param[out] ppixel rgb value
1009 * \return 0 if OK, 1 on error
1010 *
1011 * <pre>
1012 * Notes:
1013 * (1) This transformation leaves the hue invariant, while changing
1014 * the saturation and intensity. It can be used for that
1015 * purpose in pixLinearMapToTargetColor().
1016 * (2) %fraction is in the range [-1 .... +1]. If %fraction < 0,
1017 * saturation is increased and brightness is reduced. The
1018 * opposite results if %fraction > 0. If %fraction == -1,
1019 * the resulting pixel is black; %fraction == 1 results in white.
1020 * </pre>
1021 */
1022 l_int32
pixelFractionalShift(l_int32 rval,l_int32 gval,l_int32 bval,l_float32 fraction,l_uint32 * ppixel)1023 pixelFractionalShift(l_int32 rval,
1024 l_int32 gval,
1025 l_int32 bval,
1026 l_float32 fraction,
1027 l_uint32 *ppixel)
1028 {
1029 l_int32 nrval, ngval, nbval;
1030
1031 PROCNAME("pixelFractionalShift");
1032
1033 if (!ppixel)
1034 return ERROR_INT("&pixel defined", procName, 1);
1035 if (fraction < -1.0 || fraction > 1.0)
1036 return ERROR_INT("fraction not in [-1 ... +1]", procName, 1);
1037
1038 nrval = (fraction < 0) ? (l_int32)((1.0 + fraction) * rval + 0.5) :
1039 rval + (l_int32)(fraction * (255 - rval) + 0.5);
1040 ngval = (fraction < 0) ? (l_int32)((1.0 + fraction) * gval + 0.5) :
1041 gval + (l_int32)(fraction * (255 - gval) + 0.5);
1042 nbval = (fraction < 0) ? (l_int32)((1.0 + fraction) * bval + 0.5) :
1043 bval + (l_int32)(fraction * (255 - bval) + 0.5);
1044 composeRGBPixel(nrval, ngval, nbval, ppixel);
1045 return 0;
1046 }
1047