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