comparison mupdf-source/thirdparty/leptonica/src/graphics.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
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 graphics.c
29 * <pre>
30 *
31 * Pta generation for arbitrary shapes built with lines
32 * PTA *generatePtaLine()
33 * PTA *generatePtaWideLine()
34 * PTA *generatePtaBox()
35 * PTA *generatePtaBoxa()
36 * PTA *generatePtaHashBox()
37 * PTA *generatePtaHashBoxa()
38 * PTAA *generatePtaaBoxa()
39 * PTAA *generatePtaaHashBoxa()
40 * PTA *generatePtaPolyline()
41 * PTA *generatePtaGrid()
42 * PTA *convertPtaLineTo4cc()
43 * PTA *generatePtaFilledCircle()
44 * PTA *generatePtaFilledSquare()
45 * PTA *generatePtaLineFromPt()
46 * l_int32 locatePtRadially()
47 *
48 * Rendering function plots directly on images
49 * l_int32 pixRenderPlotFromNuma()
50 * l_int32 pixRenderPlotFromNumaGen()
51 * PTA *makePlotPtaFromNuma()
52 * PTA *makePlotPtaFromNumaGen()
53 *
54 * Pta rendering
55 * l_int32 pixRenderPta()
56 * l_int32 pixRenderPtaArb()
57 * l_int32 pixRenderPtaBlend()
58 *
59 * Rendering of arbitrary shapes built with lines
60 * l_int32 pixRenderLine()
61 * l_int32 pixRenderLineArb()
62 * l_int32 pixRenderLineBlend()
63 *
64 * l_int32 pixRenderBox()
65 * l_int32 pixRenderBoxArb()
66 * l_int32 pixRenderBoxBlend()
67 *
68 * l_int32 pixRenderBoxa()
69 * l_int32 pixRenderBoxaArb()
70 * l_int32 pixRenderBoxaBlend()
71 *
72 * l_int32 pixRenderHashBox()
73 * l_int32 pixRenderHashBoxArb()
74 * l_int32 pixRenderHashBoxBlend()
75 * l_int32 pixRenderHashMaskArb()
76 *
77 * l_int32 pixRenderHashBoxa()
78 * l_int32 pixRenderHashBoxaArb()
79 * l_int32 pixRenderHashBoxaBlend()
80 *
81 * l_int32 pixRenderPolyline()
82 * l_int32 pixRenderPolylineArb()
83 * l_int32 pixRenderPolylineBlend()
84 *
85 * l_int32 pixRenderGrid()
86 *
87 * l_int32 pixRenderRandomCmapPtaa()
88 *
89 * Rendering and filling of polygons
90 * PIX *pixRenderPolygon()
91 * PIX *pixFillPolygon()
92 *
93 * Contour rendering on grayscale images
94 * PIX *pixRenderContours()
95 * PIX *fpixAutoRenderContours()
96 * PIX *fpixRenderContours()
97 *
98 * Boundary pt generation on 1 bpp images
99 * PTA *pixGeneratePtaBoundary()
100 *
101 * The line rendering functions are relatively crude, but they
102 * get the job done for most simple situations. We use the pta
103 * (array of points) as an intermediate data structure. For example,
104 * to render a line we first generate a pta.
105 *
106 * Some rendering functions come in sets of three. For example
107 * pixRenderLine() -- render on 1 bpp pix
108 * pixRenderLineArb() -- render on 32 bpp pix with arbitrary (r,g,b)
109 * pixRenderLineBlend() -- render on 32 bpp pix, blending the
110 * (r,g,b) graphic object with the underlying rgb pixels.
111 *
112 * There are also procedures for plotting a function, computed
113 * from the row or column pixels, directly on the image.
114 * </pre>
115 */
116
117 #ifdef HAVE_CONFIG_H
118 #include <config_auto.h>
119 #endif /* HAVE_CONFIG_H */
120
121 #include <string.h>
122 #include <math.h>
123 #include "allheaders.h"
124
125 /*------------------------------------------------------------------*
126 * Pta generation for arbitrary shapes built with lines *
127 *------------------------------------------------------------------*/
128 /*!
129 * \brief generatePtaLine()
130 *
131 * \param[in] x1, y1 end point 1
132 * \param[in] x2, y2 end point 2
133 * \return pta, or NULL on error
134 *
135 * <pre>
136 * Notes:
137 * (1) Uses Bresenham line drawing, which results in an 8-connected line.
138 * </pre>
139 */
140 PTA *
141 generatePtaLine(l_int32 x1,
142 l_int32 y1,
143 l_int32 x2,
144 l_int32 y2)
145 {
146 l_int32 npts, diff, getyofx, sign, i, x, y;
147 l_float32 slope;
148 PTA *pta;
149
150 /* Generate line parameters */
151 if (x1 == x2 && y1 == y2) { /* same point */
152 getyofx = TRUE;
153 npts = 1;
154 } else if (L_ABS(x2 - x1) >= L_ABS(y2 - y1)) {
155 getyofx = TRUE;
156 npts = L_ABS(x2 - x1) + 1;
157 diff = x2 - x1;
158 sign = L_SIGN(x2 - x1);
159 slope = (l_float32)(sign * (y2 - y1)) / (l_float32)diff;
160 } else {
161 getyofx = FALSE;
162 npts = L_ABS(y2 - y1) + 1;
163 diff = y2 - y1;
164 sign = L_SIGN(y2 - y1);
165 slope = (l_float32)(sign * (x2 - x1)) / (l_float32)diff;
166 }
167
168 if ((pta = ptaCreate(npts)) == NULL)
169 return (PTA *)ERROR_PTR("pta not made", __func__, NULL);
170
171 if (npts == 1) { /* degenerate case */
172 ptaAddPt(pta, x1, y1);
173 return pta;
174 }
175
176 /* Generate the set of points */
177 if (getyofx) { /* y = y(x) */
178 for (i = 0; i < npts; i++) {
179 x = x1 + sign * i;
180 y = (l_int32)(y1 + (l_float32)i * slope + 0.5);
181 ptaAddPt(pta, x, y);
182 }
183 } else { /* x = x(y) */
184 for (i = 0; i < npts; i++) {
185 x = (l_int32)(x1 + (l_float32)i * slope + 0.5);
186 y = y1 + sign * i;
187 ptaAddPt(pta, x, y);
188 }
189 }
190
191 return pta;
192 }
193
194
195 /*!
196 * \brief generatePtaWideLine()
197 *
198 * \param[in] x1, y1 end point 1
199 * \param[in] x2, y2 end point 2
200 * \param[in] width
201 * \return ptaj, or NULL on error
202 */
203 PTA *
204 generatePtaWideLine(l_int32 x1,
205 l_int32 y1,
206 l_int32 x2,
207 l_int32 y2,
208 l_int32 width)
209 {
210 l_int32 i, x1a, x2a, y1a, y2a;
211 PTA *pta, *ptaj;
212
213 if (width < 1) {
214 L_WARNING("width < 1; setting to 1\n", __func__);
215 width = 1;
216 }
217
218 if ((ptaj = generatePtaLine(x1, y1, x2, y2)) == NULL)
219 return (PTA *)ERROR_PTR("ptaj not made", __func__, NULL);
220 if (width == 1)
221 return ptaj;
222
223 /* width > 1; estimate line direction & join */
224 if (L_ABS(x1 - x2) > L_ABS(y1 - y2)) { /* "horizontal" line */
225 for (i = 1; i < width; i++) {
226 if ((i & 1) == 1) { /* place above */
227 y1a = y1 - (i + 1) / 2;
228 y2a = y2 - (i + 1) / 2;
229 } else { /* place below */
230 y1a = y1 + (i + 1) / 2;
231 y2a = y2 + (i + 1) / 2;
232 }
233 if ((pta = generatePtaLine(x1, y1a, x2, y2a)) != NULL) {
234 ptaJoin(ptaj, pta, 0, -1);
235 ptaDestroy(&pta);
236 }
237 }
238 } else { /* "vertical" line */
239 for (i = 1; i < width; i++) {
240 if ((i & 1) == 1) { /* place to left */
241 x1a = x1 - (i + 1) / 2;
242 x2a = x2 - (i + 1) / 2;
243 } else { /* place to right */
244 x1a = x1 + (i + 1) / 2;
245 x2a = x2 + (i + 1) / 2;
246 }
247 if ((pta = generatePtaLine(x1a, y1, x2a, y2)) != NULL) {
248 ptaJoin(ptaj, pta, 0, -1);
249 ptaDestroy(&pta);
250 }
251 }
252 }
253
254 return ptaj;
255 }
256
257
258 /*!
259 * \brief generatePtaBox()
260 *
261 * \param[in] box
262 * \param[in] width of line
263 * \return ptad, or NULL on error
264 *
265 * <pre>
266 * Notes:
267 * (1) Because the box is constructed so that we don't have any
268 * overlapping lines, there is no need to remove duplicates.
269 * </pre>
270 */
271 PTA *
272 generatePtaBox(BOX *box,
273 l_int32 width)
274 {
275 l_int32 x, y, w, h;
276 PTA *ptad, *pta;
277
278 if (!box)
279 return (PTA *)ERROR_PTR("box not defined", __func__, NULL);
280 if (width < 1) {
281 L_WARNING("width < 1; setting to 1\n", __func__);
282 width = 1;
283 }
284
285 /* Generate line points and add them to the pta. */
286 boxGetGeometry(box, &x, &y, &w, &h);
287 if (w == 0 || h == 0)
288 return (PTA *)ERROR_PTR("box has w = 0 or h = 0", __func__, NULL);
289 ptad = ptaCreate(0);
290 if ((width & 1) == 1) { /* odd width */
291 pta = generatePtaWideLine(x - width / 2, y,
292 x + w - 1 + width / 2, y, width);
293 ptaJoin(ptad, pta, 0, -1);
294 ptaDestroy(&pta);
295 pta = generatePtaWideLine(x + w - 1, y + 1 + width / 2,
296 x + w - 1, y + h - 2 - width / 2, width);
297 ptaJoin(ptad, pta, 0, -1);
298 ptaDestroy(&pta);
299 pta = generatePtaWideLine(x + w - 1 + width / 2, y + h - 1,
300 x - width / 2, y + h - 1, width);
301 ptaJoin(ptad, pta, 0, -1);
302 ptaDestroy(&pta);
303 pta = generatePtaWideLine(x, y + h - 2 - width / 2,
304 x, y + 1 + width / 2, width);
305 ptaJoin(ptad, pta, 0, -1);
306 ptaDestroy(&pta);
307 } else { /* even width */
308 pta = generatePtaWideLine(x - width / 2, y,
309 x + w - 2 + width / 2, y, width);
310 ptaJoin(ptad, pta, 0, -1);
311 ptaDestroy(&pta);
312 pta = generatePtaWideLine(x + w - 1, y + 0 + width / 2,
313 x + w - 1, y + h - 2 - width / 2, width);
314 ptaJoin(ptad, pta, 0, -1);
315 ptaDestroy(&pta);
316 pta = generatePtaWideLine(x + w - 2 + width / 2, y + h - 1,
317 x - width / 2, y + h - 1, width);
318 ptaJoin(ptad, pta, 0, -1);
319 ptaDestroy(&pta);
320 pta = generatePtaWideLine(x, y + h - 2 - width / 2,
321 x, y + 0 + width / 2, width);
322 ptaJoin(ptad, pta, 0, -1);
323 ptaDestroy(&pta);
324 }
325
326 return ptad;
327 }
328
329
330 /*!
331 * \brief generatePtaBoxa()
332 *
333 * \param[in] boxa
334 * \param[in] width
335 * \param[in] removedups 1 to remove, 0 to leave
336 * \return ptad, or NULL on error
337 *
338 * <pre>
339 * Notes:
340 * (1) If %boxa has overlapping boxes, and if blending will
341 * be used to give a transparent effect, transparency
342 * artifacts at line intersections can be removed using
343 * %removedups = 1.
344 * </pre>
345 */
346 PTA *
347 generatePtaBoxa(BOXA *boxa,
348 l_int32 width,
349 l_int32 removedups)
350 {
351 l_int32 i, n;
352 BOX *box;
353 PTA *ptad, *ptat, *pta;
354
355 if (!boxa)
356 return (PTA *)ERROR_PTR("boxa not defined", __func__, NULL);
357 if (width < 1) {
358 L_WARNING("width < 1; setting to 1\n", __func__);
359 width = 1;
360 }
361
362 n = boxaGetCount(boxa);
363 ptat = ptaCreate(0);
364 for (i = 0; i < n; i++) {
365 box = boxaGetBox(boxa, i, L_CLONE);
366 pta = generatePtaBox(box, width);
367 ptaJoin(ptat, pta, 0, -1);
368 ptaDestroy(&pta);
369 boxDestroy(&box);
370 }
371
372 if (removedups)
373 ptaRemoveDupsByAset(ptat, &ptad);
374 else
375 ptad = ptaClone(ptat);
376
377 ptaDestroy(&ptat);
378 return ptad;
379 }
380
381
382 /*!
383 * \brief generatePtaHashBox()
384 *
385 * \param[in] box
386 * \param[in] spacing spacing between lines; must be > 1
387 * \param[in] width of line
388 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
389 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
390 * L_NEG_SLOPE_LINE
391 * \param[in] outline 0 to skip drawing box outline
392 * \return ptad, or NULL on error
393 *
394 * <pre>
395 * Notes:
396 * (1) The orientation takes on one of 4 orientations (horiz, vertical,
397 * slope +1, slope -1).
398 * (2) The full outline is also drawn if %outline = 1.
399 * </pre>
400 */
401 PTA *
402 generatePtaHashBox(BOX *box,
403 l_int32 spacing,
404 l_int32 width,
405 l_int32 orient,
406 l_int32 outline)
407 {
408 l_int32 bx, by, bh, bw, x, y, x1, y1, x2, y2, i, n, npts;
409 PTA *ptad, *pta;
410
411 if (!box)
412 return (PTA *)ERROR_PTR("box not defined", __func__, NULL);
413 if (spacing <= 1)
414 return (PTA *)ERROR_PTR("spacing not > 1", __func__, NULL);
415 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
416 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
417 return (PTA *)ERROR_PTR("invalid line orientation", __func__, NULL);
418 boxGetGeometry(box, &bx, &by, &bw, &bh);
419 if (bw == 0 || bh == 0)
420 return (PTA *)ERROR_PTR("box has bw = 0 or bh = 0", __func__, NULL);
421 if (width < 1) {
422 L_WARNING("width < 1; setting to 1\n", __func__);
423 width = 1;
424 }
425
426 /* Generate line points and add them to the pta. */
427 ptad = ptaCreate(0);
428 if (outline) {
429 pta = generatePtaBox(box, width);
430 ptaJoin(ptad, pta, 0, -1);
431 ptaDestroy(&pta);
432 }
433 if (orient == L_HORIZONTAL_LINE) {
434 n = 1 + bh / spacing;
435 for (i = 0; i < n; i++) {
436 y = by + (i * (bh - 1)) / (n - 1);
437 pta = generatePtaWideLine(bx, y, bx + bw - 1, y, width);
438 ptaJoin(ptad, pta, 0, -1);
439 ptaDestroy(&pta);
440 }
441 } else if (orient == L_VERTICAL_LINE) {
442 n = 1 + bw / spacing;
443 for (i = 0; i < n; i++) {
444 x = bx + (i * (bw - 1)) / (n - 1);
445 pta = generatePtaWideLine(x, by, x, by + bh - 1, width);
446 ptaJoin(ptad, pta, 0, -1);
447 ptaDestroy(&pta);
448 }
449 } else if (orient == L_POS_SLOPE_LINE) {
450 n = 2 + (l_int32)((bw + bh) / (1.4 * spacing));
451 for (i = 0; i < n; i++) {
452 x = (l_int32)(bx + (i + 0.5) * 1.4 * spacing);
453 boxIntersectByLine(box, x, by - 1, 1.0, &x1, &y1, &x2, &y2, &npts);
454 if (npts == 2) {
455 pta = generatePtaWideLine(x1, y1, x2, y2, width);
456 ptaJoin(ptad, pta, 0, -1);
457 ptaDestroy(&pta);
458 }
459 }
460 } else { /* orient == L_NEG_SLOPE_LINE */
461 n = 2 + (l_int32)((bw + bh) / (1.4 * spacing));
462 for (i = 0; i < n; i++) {
463 x = (l_int32)(bx - bh + (i + 0.5) * 1.4 * spacing);
464 boxIntersectByLine(box, x, by - 1, -1.0, &x1, &y1, &x2, &y2, &npts);
465 if (npts == 2) {
466 pta = generatePtaWideLine(x1, y1, x2, y2, width);
467 ptaJoin(ptad, pta, 0, -1);
468 ptaDestroy(&pta);
469 }
470 }
471 }
472
473 return ptad;
474 }
475
476
477 /*!
478 * \brief generatePtaHashBoxa()
479 *
480 * \param[in] boxa
481 * \param[in] spacing spacing between lines; must be > 1
482 * \param[in] width of line
483 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
484 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
485 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
486 * L_NEG_SLOPE_LINE
487 * \param[in] outline 0 to skip drawing box outline
488 * \param[in] removedups 1 to remove, 0 to leave
489 * \return ptad, or NULL on error
490 *
491 * <pre>
492 * Notes:
493 * (1) The orientation takes on one of 4 orientations (horiz, vertical,
494 * slope +1, slope -1).
495 * (2) The full outline is also drawn if %outline = 1.
496 * (3) If the boxa has overlapping boxes, and if blending will
497 * be used to give a transparent effect, transparency
498 * artifacts at line intersections can be removed using
499 * %removedups = 1.
500 * </pre>
501 */
502 PTA *
503 generatePtaHashBoxa(BOXA *boxa,
504 l_int32 spacing,
505 l_int32 width,
506 l_int32 orient,
507 l_int32 outline,
508 l_int32 removedups)
509 {
510 l_int32 i, n;
511 BOX *box;
512 PTA *ptad, *ptat, *pta;
513
514 if (!boxa)
515 return (PTA *)ERROR_PTR("boxa not defined", __func__, NULL);
516 if (spacing <= 1)
517 return (PTA *)ERROR_PTR("spacing not > 1", __func__, NULL);
518 if (width < 1) {
519 L_WARNING("width < 1; setting to 1\n", __func__);
520 width = 1;
521 }
522 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
523 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
524 return (PTA *)ERROR_PTR("invalid line orientation", __func__, NULL);
525
526 n = boxaGetCount(boxa);
527 ptat = ptaCreate(0);
528 for (i = 0; i < n; i++) {
529 box = boxaGetBox(boxa, i, L_CLONE);
530 pta = generatePtaHashBox(box, spacing, width, orient, outline);
531 ptaJoin(ptat, pta, 0, -1);
532 ptaDestroy(&pta);
533 boxDestroy(&box);
534 }
535
536 if (removedups)
537 ptaRemoveDupsByAset(ptat, &ptad);
538 else
539 ptad = ptaClone(ptat);
540
541 ptaDestroy(&ptat);
542 return ptad;
543 }
544
545
546 /*!
547 * \brief generatePtaaBoxa()
548 *
549 * \param[in] boxa
550 * \return ptaa, or NULL on error
551 *
552 * <pre>
553 * Notes:
554 * (1) This generates a pta of the four corners for each box in
555 * the boxa.
556 * (2) Each of these pta can be rendered onto a pix with random colors,
557 * by using pixRenderRandomCmapPtaa() with closeflag = 1.
558 * </pre>
559 */
560 PTAA *
561 generatePtaaBoxa(BOXA *boxa)
562 {
563 l_int32 i, n, x, y, w, h;
564 BOX *box;
565 PTA *pta;
566 PTAA *ptaa;
567
568 if (!boxa)
569 return (PTAA *)ERROR_PTR("boxa not defined", __func__, NULL);
570
571 n = boxaGetCount(boxa);
572 ptaa = ptaaCreate(n);
573 for (i = 0; i < n; i++) {
574 box = boxaGetBox(boxa, i, L_CLONE);
575 boxGetGeometry(box, &x, &y, &w, &h);
576 pta = ptaCreate(4);
577 ptaAddPt(pta, x, y);
578 ptaAddPt(pta, x + w - 1, y);
579 ptaAddPt(pta, x + w - 1, y + h - 1);
580 ptaAddPt(pta, x, y + h - 1);
581 ptaaAddPta(ptaa, pta, L_INSERT);
582 boxDestroy(&box);
583 }
584
585 return ptaa;
586 }
587
588
589 /*!
590 * \brief generatePtaaHashBoxa()
591 *
592 * \param[in] boxa
593 * \param[in] spacing spacing between hash lines; must be > 1
594 * \param[in] width hash line width
595 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
596 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
597 * L_NEG_SLOPE_LINE
598 * \param[in] outline 0 to skip drawing box outline
599 * \return ptaa, or NULL on error
600 *
601 * <pre>
602 * Notes:
603 * (1) The orientation takes on one of 4 orientations (horiz, vertical,
604 * slope +1, slope -1).
605 * (2) The full outline is also drawn if %outline = 1.
606 * (3) Each of these pta can be rendered onto a pix with random colors,
607 * by using pixRenderRandomCmapPtaa() with closeflag = 1.
608 *
609 * </pre>
610 */
611 PTAA *
612 generatePtaaHashBoxa(BOXA *boxa,
613 l_int32 spacing,
614 l_int32 width,
615 l_int32 orient,
616 l_int32 outline)
617 {
618 l_int32 i, n;
619 BOX *box;
620 PTA *pta;
621 PTAA *ptaa;
622
623 if (!boxa)
624 return (PTAA *)ERROR_PTR("boxa not defined", __func__, NULL);
625 if (spacing <= 1)
626 return (PTAA *)ERROR_PTR("spacing not > 1", __func__, NULL);
627 if (width < 1) {
628 L_WARNING("width < 1; setting to 1\n", __func__);
629 width = 1;
630 }
631 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
632 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
633 return (PTAA *)ERROR_PTR("invalid line orientation", __func__, NULL);
634
635 n = boxaGetCount(boxa);
636 ptaa = ptaaCreate(n);
637 for (i = 0; i < n; i++) {
638 box = boxaGetBox(boxa, i, L_CLONE);
639 pta = generatePtaHashBox(box, spacing, width, orient, outline);
640 ptaaAddPta(ptaa, pta, L_INSERT);
641 boxDestroy(&box);
642 }
643
644 return ptaa;
645 }
646
647
648 /*!
649 * \brief generatePtaPolyline()
650 *
651 * \param[in] ptas vertices of polyline
652 * \param[in] width
653 * \param[in] closeflag 1 to close the contour; 0 otherwise
654 * \param[in] removedups 1 to remove, 0 to leave
655 * \return ptad, or NULL on error
656 */
657 PTA *
658 generatePtaPolyline(PTA *ptas,
659 l_int32 width,
660 l_int32 closeflag,
661 l_int32 removedups)
662 {
663 l_int32 i, n, x1, y1, x2, y2;
664 PTA *ptad, *ptat, *pta;
665
666 if (!ptas)
667 return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL);
668 if (width < 1) {
669 L_WARNING("width < 1; setting to 1\n", __func__);
670 width = 1;
671 }
672
673 n = ptaGetCount(ptas);
674 ptat = ptaCreate(0);
675 if (n < 2) /* nothing to do */
676 return ptat;
677
678 ptaGetIPt(ptas, 0, &x1, &y1);
679 for (i = 1; i < n; i++) {
680 ptaGetIPt(ptas, i, &x2, &y2);
681 pta = generatePtaWideLine(x1, y1, x2, y2, width);
682 ptaJoin(ptat, pta, 0, -1);
683 ptaDestroy(&pta);
684 x1 = x2;
685 y1 = y2;
686 }
687
688 if (closeflag) {
689 ptaGetIPt(ptas, 0, &x2, &y2);
690 pta = generatePtaWideLine(x1, y1, x2, y2, width);
691 ptaJoin(ptat, pta, 0, -1);
692 ptaDestroy(&pta);
693 }
694
695 if (removedups)
696 ptaRemoveDupsByAset(ptat, &ptad);
697 else
698 ptad = ptaClone(ptat);
699
700 ptaDestroy(&ptat);
701 return ptad;
702 }
703
704
705 /*!
706 * \brief generatePtaGrid()
707 *
708 * \param[in] w, h of region where grid will be displayed
709 * \param[in] nx, ny number of rectangles in each direction in grid
710 * \param[in] width of rendered lines
711 * \return ptad, or NULL on error
712 */
713 PTA *
714 generatePtaGrid(l_int32 w,
715 l_int32 h,
716 l_int32 nx,
717 l_int32 ny,
718 l_int32 width)
719 {
720 l_int32 i, j, bx, by, x1, x2, y1, y2;
721 BOX *box;
722 BOXA *boxa;
723 PTA *pta;
724
725 if (nx < 1 || ny < 1)
726 return (PTA *)ERROR_PTR("nx and ny must be > 0", __func__, NULL);
727 if (w < 2 * nx || h < 2 * ny)
728 return (PTA *)ERROR_PTR("w and/or h too small", __func__, NULL);
729 if (width < 1) {
730 L_WARNING("width < 1; setting to 1\n", __func__);
731 width = 1;
732 }
733
734 boxa = boxaCreate(nx * ny);
735 bx = (w + nx - 1) / nx;
736 by = (h + ny - 1) / ny;
737 for (i = 0; i < ny; i++) {
738 y1 = by * i;
739 y2 = L_MIN(y1 + by, h - 1);
740 for (j = 0; j < nx; j++) {
741 x1 = bx * j;
742 x2 = L_MIN(x1 + bx, w - 1);
743 box = boxCreate(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
744 boxaAddBox(boxa, box, L_INSERT);
745 }
746 }
747
748 pta = generatePtaBoxa(boxa, width, 1);
749 boxaDestroy(&boxa);
750 return pta;
751 }
752
753
754 /*!
755 * \brief convertPtaLineTo4cc()
756 *
757 * \param[in] ptas 8-connected line of points
758 * \return ptad 4-connected line, or NULL on error
759 *
760 * <pre>
761 * Notes:
762 * (1) When a polyline is generated with width = 1, the resulting
763 * line is not 4-connected in general. This function adds
764 * points as necessary to convert the line to 4-cconnected.
765 * It is useful when rendering 1 bpp on a pix.
766 * (2) Do not use this for lines generated with width > 1.
767 * </pre>
768 */
769 PTA *
770 convertPtaLineTo4cc(PTA *ptas)
771 {
772 l_int32 i, n, x, y, xp, yp;
773 PTA *ptad;
774
775 if (!ptas)
776 return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL);
777
778 n = ptaGetCount(ptas);
779 ptad = ptaCreate(n);
780 ptaGetIPt(ptas, 0, &xp, &yp);
781 ptaAddPt(ptad, xp, yp);
782 for (i = 1; i < n; i++) {
783 ptaGetIPt(ptas, i, &x, &y);
784 if (x != xp && y != yp) /* diagonal */
785 ptaAddPt(ptad, x, yp);
786 ptaAddPt(ptad, x, y);
787 xp = x;
788 yp = y;
789 }
790
791 return ptad;
792 }
793
794
795 /*!
796 * \brief generatePtaFilledCircle()
797 *
798 * \param[in] radius
799 * \return pta, or NULL on error
800 *
801 * <pre>
802 * Notes:
803 * (1) The circle is has diameter = 2 * radius + 1.
804 * (2) It is located with the center of the circle at the
805 * point (%radius, %radius).
806 * (3) Consequently, it typically must be translated if
807 * it is to represent a set of pixels in an image.
808 * </pre>
809 */
810 PTA *
811 generatePtaFilledCircle(l_int32 radius)
812 {
813 l_int32 x, y;
814 l_float32 radthresh, sqdist;
815 PTA *pta;
816
817 if (radius < 1)
818 return (PTA *)ERROR_PTR("radius must be >= 1", __func__, NULL);
819
820 pta = ptaCreate(0);
821 radthresh = (radius + 0.5) * (radius + 0.5);
822 for (y = 0; y <= 2 * radius; y++) {
823 for (x = 0; x <= 2 * radius; x++) {
824 sqdist = (l_float32)((y - radius) * (y - radius) +
825 (x - radius) * (x - radius));
826 if (sqdist <= radthresh)
827 ptaAddPt(pta, x, y);
828 }
829 }
830
831 return pta;
832 }
833
834
835 /*!
836 * \brief generatePtaFilledSquare()
837 *
838 * \param[in] side
839 * \return pta, or NULL on error
840 *
841 * <pre>
842 * Notes:
843 * (1) The center of the square can be chosen to be at
844 * (side / 2, side / 2). It must be translated by this amount
845 * when used for replication.
846 * </pre>
847 */
848 PTA *
849 generatePtaFilledSquare(l_int32 side)
850 {
851 l_int32 x, y;
852 PTA *pta;
853
854 if (side < 1)
855 return (PTA *)ERROR_PTR("side must be > 0", __func__, NULL);
856
857 pta = ptaCreate(0);
858 for (y = 0; y < side; y++)
859 for (x = 0; x < side; x++)
860 ptaAddPt(pta, x, y);
861
862 return pta;
863 }
864
865
866 /*!
867 * \brief generatePtaLineFromPt()
868 *
869 * \param[in] x, y point of origination
870 * \param[in] length of line, including starting point
871 * \param[in] radang angle in radians, CW from horizontal
872 * \return pta, or NULL on error
873 *
874 * <pre>
875 * Notes:
876 * (1) %length of the line is 1 greater than the distance
877 * used in locatePtRadially(). Example: a distance of 1
878 * gives rise to a length of 2.
879 * </pre>
880 */
881 PTA *
882 generatePtaLineFromPt(l_int32 x,
883 l_int32 y,
884 l_float64 length,
885 l_float64 radang)
886 {
887 l_int32 x2, y2; /* the point at the other end of the line */
888
889 x2 = x + (l_int32)((length - 1.0) * cos(radang));
890 y2 = y + (l_int32)((length - 1.0) * sin(radang));
891 return generatePtaLine(x, y, x2, y2);
892 }
893
894
895 /*!
896 * \brief locatePtRadially()
897 *
898 * \param[in] xr, yr reference point
899 * \param[in] radang angle in radians, CW from horizontal
900 * \param[in] dist distance of point from reference point along
901 * line given by the specified angle
902 * \param[out] px, py location of point
903 * \return 0 if OK, 1 on error
904 */
905 l_ok
906 locatePtRadially(l_int32 xr,
907 l_int32 yr,
908 l_float64 dist,
909 l_float64 radang,
910 l_float64 *px,
911 l_float64 *py)
912 {
913 if (!px || !py)
914 return ERROR_INT("&x and &y not both defined", __func__, 1);
915
916 *px = xr + dist * cos(radang);
917 *py = yr + dist * sin(radang);
918 return 0;
919 }
920
921
922 /*------------------------------------------------------------------*
923 * Rendering function plots directly on images *
924 *------------------------------------------------------------------*/
925 /*!
926 * \brief pixRenderPlotFromNuma()
927 *
928 * \param[in,out] ppix any type; replaced if not 32 bpp rgb
929 * \param[in] na to be plotted
930 * \param[in] plotloc location of plot: L_PLOT_AT_TOP, etc
931 * \param[in] linewidth width of "line" that is drawn; between 1 and 7
932 * \param[in] max maximum excursion in pixels from baseline
933 * \param[in] color plot color: 0xrrggbb00
934 * \return 0 if OK, 1 on error
935 *
936 * <pre>
937 * Notes:
938 * (1) Simplified interface for plotting row or column aligned data
939 * on a pix.
940 * (2) This replaces %pix with a 32 bpp rgb version if it is not
941 * already 32 bpp. It then draws the plot on the pix.
942 * (3) See makePlotPtaFromNumaGen() for more details.
943 * </pre>
944 */
945 l_ok
946 pixRenderPlotFromNuma(PIX **ppix,
947 NUMA *na,
948 l_int32 plotloc,
949 l_int32 linewidth,
950 l_int32 max,
951 l_uint32 color)
952 {
953 l_int32 w, h, size, rval, gval, bval;
954 PIX *pix1;
955 PTA *pta;
956
957 if (!ppix)
958 return ERROR_INT("&pix not defined", __func__, 1);
959 if (*ppix == NULL)
960 return ERROR_INT("pix not defined", __func__, 1);
961
962 pixGetDimensions(*ppix, &w, &h, NULL);
963 size = (plotloc == L_PLOT_AT_TOP || plotloc == L_PLOT_AT_MID_HORIZ ||
964 plotloc == L_PLOT_AT_BOT) ? h : w;
965 pta = makePlotPtaFromNuma(na, size, plotloc, linewidth, max);
966 if (!pta)
967 return ERROR_INT("pta not made", __func__, 1);
968
969 if (pixGetDepth(*ppix) != 32) {
970 pix1 = pixConvertTo32(*ppix);
971 pixDestroy(ppix);
972 *ppix = pix1;
973 }
974 extractRGBValues(color, &rval, &gval, &bval);
975 pixRenderPtaArb(*ppix, pta, rval, gval, bval);
976 ptaDestroy(&pta);
977 return 0;
978 }
979
980
981 /*!
982 * \brief makePlotPtaFromNuma()
983 *
984 * \param[in] na
985 * \param[in] size pix height for horizontal plot; pix width
986 * for vertical plot
987 * \param[in] plotloc location of plot: L_PLOT_AT_TOP, etc
988 * \param[in] linewidth width of "line" that is drawn; between 1 and 7
989 * \param[in] max maximum excursion in pixels from baseline
990 * \return ptad, or NULL on error
991 *
992 * <pre>
993 * Notes:
994 * (1) This generates points from %numa representing y(x) or x(y)
995 * with respect to a pix. A horizontal plot y(x) is drawn for
996 * a function of column position, and a vertical plot is drawn
997 * for a function x(y) of row position. The baseline is located
998 * so that all plot points will fit in the pix.
999 * (2) See makePlotPtaFromNumaGen() for more details.
1000 * </pre>
1001 */
1002 PTA *
1003 makePlotPtaFromNuma(NUMA *na,
1004 l_int32 size,
1005 l_int32 plotloc,
1006 l_int32 linewidth,
1007 l_int32 max)
1008 {
1009 l_int32 orient, refpos;
1010
1011 if (!na)
1012 return (PTA *)ERROR_PTR("na not defined", __func__, NULL);
1013 if (plotloc == L_PLOT_AT_TOP || plotloc == L_PLOT_AT_MID_HORIZ ||
1014 plotloc == L_PLOT_AT_BOT) {
1015 orient = L_HORIZONTAL_LINE;
1016 } else if (plotloc == L_PLOT_AT_LEFT || plotloc == L_PLOT_AT_MID_VERT ||
1017 plotloc == L_PLOT_AT_RIGHT) {
1018 orient = L_VERTICAL_LINE;
1019 } else {
1020 return (PTA *)ERROR_PTR("invalid plotloc", __func__, NULL);
1021 }
1022
1023 if (plotloc == L_PLOT_AT_LEFT || plotloc == L_PLOT_AT_TOP)
1024 refpos = max;
1025 else if (plotloc == L_PLOT_AT_MID_VERT || plotloc == L_PLOT_AT_MID_HORIZ)
1026 refpos = size / 2;
1027 else /* L_PLOT_AT_RIGHT || L_PLOT_AT_BOT */
1028 refpos = size - max - 1;
1029
1030 return makePlotPtaFromNumaGen(na, orient, linewidth, refpos, max, 1);
1031 }
1032
1033
1034 /*!
1035 * \brief pixRenderPlotFromNumaGen()
1036 *
1037 * \param[in,out] ppix any type; replaced if not 32 bpp rgb
1038 * \param[in] na to be plotted
1039 * \param[in] orient L_HORIZONTAL_LINE, L_VERTICAL_LINE
1040 * \param[in] linewidth width of "line" that is drawn; between 1 and 7
1041 * \param[in] refpos reference position: y for horizontal;
1042 * x for vertical
1043 * \param[in] max maximum excursion in pixels from baseline
1044 * \param[in] drawref 1 to draw the reference line and its normal
1045 * \param[in] color plot color: 0xrrggbb00
1046 * \return 0 if OK, 1 on error
1047 *
1048 * <pre>
1049 * Notes:
1050 * (1) General interface for plotting row or column aligned data
1051 * on a pix.
1052 * (2) This replaces %pix with a 32 bpp rgb version if it is not
1053 * already 32 bpp. It then draws the plot on the pix.
1054 * (3) See makePlotPtaFromNumaGen() for other input parameters.
1055 * </pre>
1056 */
1057 l_ok
1058 pixRenderPlotFromNumaGen(PIX **ppix,
1059 NUMA *na,
1060 l_int32 orient,
1061 l_int32 linewidth,
1062 l_int32 refpos,
1063 l_int32 max,
1064 l_int32 drawref,
1065 l_uint32 color)
1066 {
1067 l_int32 rval, gval, bval;
1068 PIX *pix1;
1069 PTA *pta;
1070
1071 if (!ppix)
1072 return ERROR_INT("&pix not defined", __func__, 1);
1073 if (*ppix == NULL)
1074 return ERROR_INT("pix not defined", __func__, 1);
1075
1076 pta = makePlotPtaFromNumaGen(na, orient, linewidth, refpos, max, drawref);
1077 if (!pta)
1078 return ERROR_INT("pta not made", __func__, 1);
1079
1080 if (pixGetDepth(*ppix) != 32) {
1081 pix1 = pixConvertTo32(*ppix);
1082 pixDestroy(ppix);
1083 *ppix = pix1;
1084 }
1085 extractRGBValues(color, &rval, &gval, &bval);
1086 pixRenderPtaArb(*ppix, pta, rval, gval, bval);
1087 ptaDestroy(&pta);
1088 return 0;
1089 }
1090
1091
1092 /*!
1093 * \brief makePlotPtaFromNumaGen()
1094 *
1095 * \param[in] na
1096 * \param[in] orient L_HORIZONTAL_LINE, L_VERTICAL_LINE
1097 * \param[in] linewidth width of "line" that is drawn; between 1 and 7
1098 * \param[in] refpos reference position: y for horizontal;
1099 * x for vertical
1100 * \param[in] max maximum excursion in pixels from baseline
1101 * \param[in] drawref 1 to draw the reference line and its normal
1102 * \return ptad, or NULL on error
1103 *
1104 * <pre>
1105 * Notes:
1106 * (1) This generates points from %numa representing y(x) or x(y)
1107 * with respect to a pix. For y(x), we draw a horizontal line
1108 * at the reference position and a vertical line at the edge; then
1109 * we draw the values of %numa, scaled so that the maximum
1110 * excursion from the reference position is %max pixels.
1111 * (2) The start and delx parameters of %numa are used to refer
1112 * its values to the raster lines (L_VERTICAL_LINE) or columns
1113 * (L_HORIZONTAL_LINE).
1114 * (3) The linewidth is chosen in the interval [1 ... 7].
1115 * (4) %refpos should be chosen so the plot is entirely within the pix
1116 * that it will be painted onto.
1117 * (5) This would typically be used to plot, in place, a function
1118 * computed along pixel rows or columns.
1119 * </pre>
1120 */
1121 PTA *
1122 makePlotPtaFromNumaGen(NUMA *na,
1123 l_int32 orient,
1124 l_int32 linewidth,
1125 l_int32 refpos,
1126 l_int32 max,
1127 l_int32 drawref)
1128 {
1129 l_int32 i, n, maxw, maxh;
1130 l_float32 minval, maxval, absval, val, scale, start, del;
1131 PTA *pta1, *pta2, *ptad;
1132
1133 if (!na)
1134 return (PTA *)ERROR_PTR("na not defined", __func__, NULL);
1135 if (orient != L_HORIZONTAL_LINE && orient != L_VERTICAL_LINE)
1136 return (PTA *)ERROR_PTR("invalid orient", __func__, NULL);
1137 if (linewidth < 1) {
1138 L_WARNING("linewidth < 1; setting to 1\n", __func__);
1139 linewidth = 1;
1140 }
1141 if (linewidth > 7) {
1142 L_WARNING("linewidth > 7; setting to 7\n", __func__);
1143 linewidth = 7;
1144 }
1145
1146 numaGetMin(na, &minval, NULL);
1147 numaGetMax(na, &maxval, NULL);
1148 absval = L_MAX(L_ABS(minval), L_ABS(maxval));
1149 scale = (l_float32)max / (l_float32)absval;
1150 n = numaGetCount(na);
1151 numaGetParameters(na, &start, &del);
1152
1153 /* Generate the plot points */
1154 pta1 = ptaCreate(n);
1155 maxw = maxh = 0;
1156 for (i = 0; i < n; i++) {
1157 numaGetFValue(na, i, &val);
1158 if (orient == L_HORIZONTAL_LINE) {
1159 ptaAddPt(pta1, start + i * del, refpos + scale * val);
1160 maxw = (del >= 0) ? start + n * del + linewidth
1161 : start + linewidth;
1162 maxh = refpos + max + linewidth;
1163 } else { /* vertical line */
1164 ptaAddPt(pta1, refpos + scale * val, start + i * del);
1165 maxw = refpos + max + linewidth;
1166 maxh = (del >= 0) ? start + n * del + linewidth
1167 : start + linewidth;
1168 }
1169 }
1170
1171 /* Optionally, widen the plot */
1172 if (linewidth > 1) {
1173 if (linewidth % 2 == 0) /* even linewidth; use side of a square */
1174 pta2 = generatePtaFilledSquare(linewidth);
1175 else /* odd linewidth; use radius of a circle */
1176 pta2 = generatePtaFilledCircle(linewidth / 2);
1177 ptad = ptaReplicatePattern(pta1, NULL, pta2, linewidth / 2,
1178 linewidth / 2, maxw, maxh);
1179 ptaDestroy(&pta2);
1180 } else {
1181 ptad = ptaClone(pta1);
1182 }
1183 ptaDestroy(&pta1);
1184
1185 /* Optionally, add the reference lines */
1186 if (drawref) {
1187 if (orient == L_HORIZONTAL_LINE) {
1188 pta1 = generatePtaLine(start, refpos, start + n * del, refpos);
1189 ptaJoin(ptad, pta1, 0, -1);
1190 ptaDestroy(&pta1);
1191 pta1 = generatePtaLine(start, refpos - max,
1192 start, refpos + max);
1193 ptaJoin(ptad, pta1, 0, -1);
1194 } else { /* vertical line */
1195 pta1 = generatePtaLine(refpos, start, refpos, start + n * del);
1196 ptaJoin(ptad, pta1, 0, -1);
1197 ptaDestroy(&pta1);
1198 pta1 = generatePtaLine(refpos - max, start,
1199 refpos + max, start);
1200 ptaJoin(ptad, pta1, 0, -1);
1201 }
1202 ptaDestroy(&pta1);
1203 }
1204
1205 return ptad;
1206 }
1207
1208
1209 /*------------------------------------------------------------------*
1210 * Pta generation for arbitrary shapes built with lines *
1211 *------------------------------------------------------------------*/
1212 /*!
1213 * \brief pixRenderPta()
1214 *
1215 * \param[in] pix any depth, not cmapped
1216 * \param[in] pta arbitrary set of points
1217 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
1218 * \return 0 if OK, 1 on error
1219 *
1220 * <pre>
1221 * Notes:
1222 * (1) L_SET_PIXELS puts all image bits in each pixel to 1
1223 * (black for 1 bpp; white for depth > 1)
1224 * (2) L_CLEAR_PIXELS puts all image bits in each pixel to 0
1225 * (white for 1 bpp; black for depth > 1)
1226 * (3) L_FLIP_PIXELS reverses all image bits in each pixel
1227 * (4) This function clips the rendering to the pix. It performs
1228 * clipping for functions such as pixRenderLine(),
1229 * pixRenderBox() and pixRenderBoxa(), that call pixRenderPta().
1230 * </pre>
1231 */
1232 l_ok
1233 pixRenderPta(PIX *pix,
1234 PTA *pta,
1235 l_int32 op)
1236 {
1237 l_int32 i, n, x, y, w, h, d, maxval;
1238
1239 if (!pix)
1240 return ERROR_INT("pix not defined", __func__, 1);
1241 if (pixGetColormap(pix))
1242 return ERROR_INT("pix is colormapped", __func__, 1);
1243 if (!pta)
1244 return ERROR_INT("pta not defined", __func__, 1);
1245 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
1246 return ERROR_INT("invalid op", __func__, 1);
1247
1248 pixGetDimensions(pix, &w, &h, &d);
1249 maxval = 1;
1250 if (op == L_SET_PIXELS) {
1251 switch (d)
1252 {
1253 case 2:
1254 maxval = 0x3;
1255 break;
1256 case 4:
1257 maxval = 0xf;
1258 break;
1259 case 8:
1260 maxval = 0xff;
1261 break;
1262 case 16:
1263 maxval = 0xffff;
1264 break;
1265 case 32:
1266 maxval = 0xffffffff;
1267 break;
1268 }
1269 }
1270
1271 n = ptaGetCount(pta);
1272 for (i = 0; i < n; i++) {
1273 ptaGetIPt(pta, i, &x, &y);
1274 if (x < 0 || x >= w)
1275 continue;
1276 if (y < 0 || y >= h)
1277 continue;
1278 switch (op)
1279 {
1280 case L_SET_PIXELS:
1281 pixSetPixel(pix, x, y, maxval);
1282 break;
1283 case L_CLEAR_PIXELS:
1284 pixClearPixel(pix, x, y);
1285 break;
1286 case L_FLIP_PIXELS:
1287 pixFlipPixel(pix, x, y);
1288 break;
1289 default:
1290 break;
1291 }
1292 }
1293
1294 return 0;
1295 }
1296
1297
1298 /*!
1299 * \brief pixRenderPtaArb()
1300 *
1301 * \param[in] pix any depth, cmapped ok
1302 * \param[in] pta arbitrary set of points
1303 * \param[in] rval, gval, bval
1304 * \return 0 if OK, 1 on error
1305 *
1306 * <pre>
1307 * Notes:
1308 * (1) If %pix is colormapped, render this color (or the nearest
1309 * color if the cmap is full) on each pixel.
1310 * (2) The rgb components have the standard dynamic range [0 ... 255]
1311 * (3) If pix is not colormapped, do the best job you can using
1312 * the input colors:
1313 * ~ d = 1: set the pixels
1314 * ~ d = 2, 4, 8: average the input rgb value
1315 * ~ d = 32: use the input rgb value
1316 * (4) This function clips the rendering to %pix.
1317 * </pre>
1318 */
1319 l_ok
1320 pixRenderPtaArb(PIX *pix,
1321 PTA *pta,
1322 l_uint8 rval,
1323 l_uint8 gval,
1324 l_uint8 bval)
1325 {
1326 l_int32 i, n, x, y, w, h, d, index;
1327 l_uint8 val;
1328 l_uint32 val32;
1329 PIXCMAP *cmap;
1330
1331 if (!pix)
1332 return ERROR_INT("pix not defined", __func__, 1);
1333 if (!pta)
1334 return ERROR_INT("pta not defined", __func__, 1);
1335 d = pixGetDepth(pix);
1336 if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32)
1337 return ERROR_INT("depth not in {1,2,4,8,32}", __func__, 1);
1338
1339 if (d == 1) {
1340 pixRenderPta(pix, pta, L_SET_PIXELS);
1341 return 0;
1342 }
1343
1344 cmap = pixGetColormap(pix);
1345 pixGetDimensions(pix, &w, &h, &d);
1346 if (cmap) {
1347 pixcmapAddNearestColor(cmap, rval, gval, bval, &index);
1348 } else {
1349 if (d == 2)
1350 val = (rval + gval + bval) / (3 * 64);
1351 else if (d == 4)
1352 val = (rval + gval + bval) / (3 * 16);
1353 else if (d == 8)
1354 val = (rval + gval + bval) / 3;
1355 else /* d == 32 */
1356 composeRGBPixel(rval, gval, bval, &val32);
1357 }
1358
1359 n = ptaGetCount(pta);
1360 for (i = 0; i < n; i++) {
1361 ptaGetIPt(pta, i, &x, &y);
1362 if (x < 0 || x >= w)
1363 continue;
1364 if (y < 0 || y >= h)
1365 continue;
1366 if (cmap)
1367 pixSetPixel(pix, x, y, index);
1368 else if (d == 32)
1369 pixSetPixel(pix, x, y, val32);
1370 else
1371 pixSetPixel(pix, x, y, val);
1372 }
1373
1374 return 0;
1375 }
1376
1377
1378 /*!
1379 * \brief pixRenderPtaBlend()
1380 *
1381 * \param[in] pix 32 bpp rgb
1382 * \param[in] pta arbitrary set of points
1383 * \param[in] rval, gval, bval
1384 * \param[in] fract
1385 * \return 0 if OK, 1 on error
1386 *
1387 * <pre>
1388 * Notes:
1389 * (1) This function clips the rendering to %pix.
1390 * </pre>
1391 */
1392 l_ok
1393 pixRenderPtaBlend(PIX *pix,
1394 PTA *pta,
1395 l_uint8 rval,
1396 l_uint8 gval,
1397 l_uint8 bval,
1398 l_float32 fract)
1399 {
1400 l_int32 i, n, x, y, w, h;
1401 l_uint8 nrval, ngval, nbval;
1402 l_uint32 val32;
1403 l_float32 frval, fgval, fbval;
1404
1405 if (!pix)
1406 return ERROR_INT("pix not defined", __func__, 1);
1407 if (!pta)
1408 return ERROR_INT("pta not defined", __func__, 1);
1409 if (pixGetDepth(pix) != 32)
1410 return ERROR_INT("depth not 32 bpp", __func__, 1);
1411 if (fract < 0.0 || fract > 1.0) {
1412 L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__);
1413 fract = 0.5;
1414 }
1415
1416 pixGetDimensions(pix, &w, &h, NULL);
1417 n = ptaGetCount(pta);
1418 frval = fract * rval;
1419 fgval = fract * gval;
1420 fbval = fract * bval;
1421 for (i = 0; i < n; i++) {
1422 ptaGetIPt(pta, i, &x, &y);
1423 if (x < 0 || x >= w)
1424 continue;
1425 if (y < 0 || y >= h)
1426 continue;
1427 pixGetPixel(pix, x, y, &val32);
1428 nrval = GET_DATA_BYTE(&val32, COLOR_RED);
1429 nrval = (l_uint8)((1. - fract) * nrval + frval);
1430 ngval = GET_DATA_BYTE(&val32, COLOR_GREEN);
1431 ngval = (l_uint8)((1. - fract) * ngval + fgval);
1432 nbval = GET_DATA_BYTE(&val32, COLOR_BLUE);
1433 nbval = (l_uint8)((1. - fract) * nbval + fbval);
1434 composeRGBPixel(nrval, ngval, nbval, &val32);
1435 pixSetPixel(pix, x, y, val32);
1436 }
1437
1438 return 0;
1439 }
1440
1441
1442 /*------------------------------------------------------------------*
1443 * Rendering of arbitrary shapes built with lines *
1444 *------------------------------------------------------------------*/
1445 /*!
1446 * \brief pixRenderLine()
1447 *
1448 * \param[in] pix any depth, not cmapped
1449 * \param[in] x1, y1
1450 * \param[in] x2, y2
1451 * \param[in] width thickness of line
1452 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
1453 * \return 0 if OK, 1 on error
1454 */
1455 l_ok
1456 pixRenderLine(PIX *pix,
1457 l_int32 x1,
1458 l_int32 y1,
1459 l_int32 x2,
1460 l_int32 y2,
1461 l_int32 width,
1462 l_int32 op)
1463 {
1464 PTA *pta;
1465
1466 if (!pix)
1467 return ERROR_INT("pix not defined", __func__, 1);
1468 if (width < 1) {
1469 L_WARNING("width must be > 0; setting to 1\n", __func__);
1470 width = 1;
1471 }
1472 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
1473 return ERROR_INT("invalid op", __func__, 1);
1474
1475 if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
1476 return ERROR_INT("pta not made", __func__, 1);
1477 pixRenderPta(pix, pta, op);
1478 ptaDestroy(&pta);
1479 return 0;
1480 }
1481
1482
1483 /*!
1484 * \brief pixRenderLineArb()
1485 *
1486 * \param[in] pix any depth, cmapped ok
1487 * \param[in] x1, y1
1488 * \param[in] x2, y2
1489 * \param[in] width thickness of line
1490 * \param[in] rval, gval, bval
1491 * \return 0 if OK, 1 on error
1492 */
1493 l_ok
1494 pixRenderLineArb(PIX *pix,
1495 l_int32 x1,
1496 l_int32 y1,
1497 l_int32 x2,
1498 l_int32 y2,
1499 l_int32 width,
1500 l_uint8 rval,
1501 l_uint8 gval,
1502 l_uint8 bval)
1503 {
1504 PTA *pta;
1505
1506 if (!pix)
1507 return ERROR_INT("pix not defined", __func__, 1);
1508 if (width < 1) {
1509 L_WARNING("width must be > 0; setting to 1\n", __func__);
1510 width = 1;
1511 }
1512
1513 if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
1514 return ERROR_INT("pta not made", __func__, 1);
1515 pixRenderPtaArb(pix, pta, rval, gval, bval);
1516 ptaDestroy(&pta);
1517 return 0;
1518 }
1519
1520
1521 /*!
1522 * \brief pixRenderLineBlend()
1523 *
1524 * \param[in] pix 32 bpp rgb
1525 * \param[in] x1, y1
1526 * \param[in] x2, y2
1527 * \param[in] width thickness of line
1528 * \param[in] rval, gval, bval
1529 * \param[in] fract
1530 * \return 0 if OK, 1 on error
1531 */
1532 l_ok
1533 pixRenderLineBlend(PIX *pix,
1534 l_int32 x1,
1535 l_int32 y1,
1536 l_int32 x2,
1537 l_int32 y2,
1538 l_int32 width,
1539 l_uint8 rval,
1540 l_uint8 gval,
1541 l_uint8 bval,
1542 l_float32 fract)
1543 {
1544 PTA *pta;
1545
1546 if (!pix)
1547 return ERROR_INT("pix not defined", __func__, 1);
1548 if (width < 1) {
1549 L_WARNING("width must be > 0; setting to 1\n", __func__);
1550 width = 1;
1551 }
1552
1553 if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
1554 return ERROR_INT("pta not made", __func__, 1);
1555 pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
1556 ptaDestroy(&pta);
1557 return 0;
1558 }
1559
1560
1561 /*!
1562 * \brief pixRenderBox()
1563 *
1564 * \param[in] pix any depth, not cmapped
1565 * \param[in] box
1566 * \param[in] width thickness of box lines
1567 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
1568 * \return 0 if OK, 1 on error
1569 */
1570 l_ok
1571 pixRenderBox(PIX *pix,
1572 BOX *box,
1573 l_int32 width,
1574 l_int32 op)
1575 {
1576 PTA *pta;
1577
1578 if (!pix)
1579 return ERROR_INT("pix not defined", __func__, 1);
1580 if (!box)
1581 return ERROR_INT("box not defined", __func__, 1);
1582 if (width < 1) {
1583 L_WARNING("width < 1; setting to 1\n", __func__);
1584 width = 1;
1585 }
1586 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
1587 return ERROR_INT("invalid op", __func__, 1);
1588
1589 if ((pta = generatePtaBox(box, width)) == NULL)
1590 return ERROR_INT("pta not made", __func__, 1);
1591 pixRenderPta(pix, pta, op);
1592 ptaDestroy(&pta);
1593 return 0;
1594 }
1595
1596
1597 /*!
1598 * \brief pixRenderBoxArb()
1599 *
1600 * \param[in] pix any depth, cmapped ok
1601 * \param[in] box
1602 * \param[in] width thickness of box lines
1603 * \param[in] rval, gval, bval
1604 * \return 0 if OK, 1 on error
1605 */
1606 l_ok
1607 pixRenderBoxArb(PIX *pix,
1608 BOX *box,
1609 l_int32 width,
1610 l_uint8 rval,
1611 l_uint8 gval,
1612 l_uint8 bval)
1613 {
1614 PTA *pta;
1615
1616 if (!pix)
1617 return ERROR_INT("pix not defined", __func__, 1);
1618 if (!box)
1619 return ERROR_INT("box not defined", __func__, 1);
1620 if (width < 1) {
1621 L_WARNING("width < 1; setting to 1\n", __func__);
1622 width = 1;
1623 }
1624
1625 if ((pta = generatePtaBox(box, width)) == NULL)
1626 return ERROR_INT("pta not made", __func__, 1);
1627 pixRenderPtaArb(pix, pta, rval, gval, bval);
1628 ptaDestroy(&pta);
1629 return 0;
1630 }
1631
1632
1633 /*!
1634 * \brief pixRenderBoxBlend()
1635 *
1636 * \param[in] pix 32 bpp rgb
1637 * \param[in] box
1638 * \param[in] width thickness of box lines
1639 * \param[in] rval, gval, bval
1640 * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
1641 * 0.0 is complete transparency (no effect)
1642 * \return 0 if OK, 1 on error
1643 */
1644 l_ok
1645 pixRenderBoxBlend(PIX *pix,
1646 BOX *box,
1647 l_int32 width,
1648 l_uint8 rval,
1649 l_uint8 gval,
1650 l_uint8 bval,
1651 l_float32 fract)
1652 {
1653 PTA *pta;
1654
1655 if (!pix)
1656 return ERROR_INT("pix not defined", __func__, 1);
1657 if (!box)
1658 return ERROR_INT("box not defined", __func__, 1);
1659 if (width < 1) {
1660 L_WARNING("width < 1; setting to 1\n", __func__);
1661 width = 1;
1662 }
1663
1664 if ((pta = generatePtaBox(box, width)) == NULL)
1665 return ERROR_INT("pta not made", __func__, 1);
1666 pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
1667 ptaDestroy(&pta);
1668 return 0;
1669 }
1670
1671
1672 /*!
1673 * \brief pixRenderBoxa()
1674 *
1675 * \param[in] pix any depth, not cmapped
1676 * \param[in] boxa
1677 * \param[in] width thickness of line
1678 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
1679 * \return 0 if OK, 1 on error
1680 */
1681 l_ok
1682 pixRenderBoxa(PIX *pix,
1683 BOXA *boxa,
1684 l_int32 width,
1685 l_int32 op)
1686 {
1687 PTA *pta;
1688
1689 if (!pix)
1690 return ERROR_INT("pix not defined", __func__, 1);
1691 if (!boxa)
1692 return ERROR_INT("boxa not defined", __func__, 1);
1693 if (width < 1) {
1694 L_WARNING("width < 1; setting to 1\n", __func__);
1695 width = 1;
1696 }
1697 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
1698 return ERROR_INT("invalid op", __func__, 1);
1699
1700 if ((pta = generatePtaBoxa(boxa, width, 0)) == NULL)
1701 return ERROR_INT("pta not made", __func__, 1);
1702 pixRenderPta(pix, pta, op);
1703 ptaDestroy(&pta);
1704 return 0;
1705 }
1706
1707
1708 /*!
1709 * \brief pixRenderBoxaArb()
1710 *
1711 * \param[in] pix any depth; colormapped is ok
1712 * \param[in] boxa
1713 * \param[in] width thickness of line
1714 * \param[in] rval, gval, bval
1715 * \return 0 if OK, 1 on error
1716 */
1717 l_ok
1718 pixRenderBoxaArb(PIX *pix,
1719 BOXA *boxa,
1720 l_int32 width,
1721 l_uint8 rval,
1722 l_uint8 gval,
1723 l_uint8 bval)
1724 {
1725 PTA *pta;
1726
1727 if (!pix)
1728 return ERROR_INT("pix not defined", __func__, 1);
1729 if (!boxa)
1730 return ERROR_INT("boxa not defined", __func__, 1);
1731 if (width < 1) {
1732 L_WARNING("width < 1; setting to 1\n", __func__);
1733 width = 1;
1734 }
1735
1736 if ((pta = generatePtaBoxa(boxa, width, 0)) == NULL)
1737 return ERROR_INT("pta not made", __func__, 1);
1738 pixRenderPtaArb(pix, pta, rval, gval, bval);
1739 ptaDestroy(&pta);
1740 return 0;
1741 }
1742
1743
1744 /*!
1745 * \brief pixRenderBoxaBlend()
1746 *
1747 * \param[in] pix 32 bpp rgb
1748 * \param[in] boxa
1749 * \param[in] width thickness of line
1750 * \param[in] rval, gval, bval
1751 * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
1752 * 0.0 is complete transparency (no effect)
1753 * \param[in] removedups 1 to remove; 0 otherwise
1754 * \return 0 if OK, 1 on error
1755 */
1756 l_ok
1757 pixRenderBoxaBlend(PIX *pix,
1758 BOXA *boxa,
1759 l_int32 width,
1760 l_uint8 rval,
1761 l_uint8 gval,
1762 l_uint8 bval,
1763 l_float32 fract,
1764 l_int32 removedups)
1765 {
1766 PTA *pta;
1767
1768 if (!pix)
1769 return ERROR_INT("pix not defined", __func__, 1);
1770 if (!boxa)
1771 return ERROR_INT("boxa not defined", __func__, 1);
1772 if (width < 1) {
1773 L_WARNING("width < 1; setting to 1\n", __func__);
1774 width = 1;
1775 }
1776
1777 if ((pta = generatePtaBoxa(boxa, width, removedups)) == NULL)
1778 return ERROR_INT("pta not made", __func__, 1);
1779 pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
1780 ptaDestroy(&pta);
1781 return 0;
1782 }
1783
1784
1785 /*!
1786 * \brief pixRenderHashBox()
1787 *
1788 * \param[in] pix any depth, not cmapped
1789 * \param[in] box
1790 * \param[in] spacing spacing between lines; must be > 1
1791 * \param[in] width thickness of box and hash lines
1792 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
1793 * \param[in] outline 0 to skip drawing box outline
1794 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
1795 * \return 0 if OK, 1 on error
1796 */
1797 l_ok
1798 pixRenderHashBox(PIX *pix,
1799 BOX *box,
1800 l_int32 spacing,
1801 l_int32 width,
1802 l_int32 orient,
1803 l_int32 outline,
1804 l_int32 op)
1805 {
1806 PTA *pta;
1807
1808 if (!pix)
1809 return ERROR_INT("pix not defined", __func__, 1);
1810 if (!box)
1811 return ERROR_INT("box not defined", __func__, 1);
1812 if (spacing <= 1)
1813 return ERROR_INT("spacing not > 1", __func__, 1);
1814 if (width < 1) {
1815 L_WARNING("width < 1; setting to 1\n", __func__);
1816 width = 1;
1817 }
1818 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
1819 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
1820 return ERROR_INT("invalid line orientation", __func__, 1);
1821 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
1822 return ERROR_INT("invalid op", __func__, 1);
1823
1824 pta = generatePtaHashBox(box, spacing, width, orient, outline);
1825 if (!pta)
1826 return ERROR_INT("pta not made", __func__, 1);
1827 pixRenderPta(pix, pta, op);
1828 ptaDestroy(&pta);
1829 return 0;
1830 }
1831
1832
1833 /*!
1834 * \brief pixRenderHashBoxArb()
1835 *
1836 * \param[in] pix any depth; cmapped ok
1837 * \param[in] box
1838 * \param[in] spacing spacing between lines; must be > 1
1839 * \param[in] width thickness of box and hash lines
1840 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
1841 * \param[in] outline 0 to skip drawing box outline
1842 * \param[in] rval, gval, bval
1843 * \return 0 if OK, 1 on error
1844 */
1845 l_ok
1846 pixRenderHashBoxArb(PIX *pix,
1847 BOX *box,
1848 l_int32 spacing,
1849 l_int32 width,
1850 l_int32 orient,
1851 l_int32 outline,
1852 l_int32 rval,
1853 l_int32 gval,
1854 l_int32 bval)
1855 {
1856 PTA *pta;
1857
1858 if (!pix)
1859 return ERROR_INT("pix not defined", __func__, 1);
1860 if (!box)
1861 return ERROR_INT("box not defined", __func__, 1);
1862 if (spacing <= 1)
1863 return ERROR_INT("spacing not > 1", __func__, 1);
1864 if (width < 1) {
1865 L_WARNING("width < 1; setting to 1\n", __func__);
1866 width = 1;
1867 }
1868 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
1869 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
1870 return ERROR_INT("invalid line orientation", __func__, 1);
1871
1872 pta = generatePtaHashBox(box, spacing, width, orient, outline);
1873 if (!pta)
1874 return ERROR_INT("pta not made", __func__, 1);
1875 pixRenderPtaArb(pix, pta, rval, gval, bval);
1876 ptaDestroy(&pta);
1877 return 0;
1878 }
1879
1880
1881 /*!
1882 * \brief pixRenderHashBoxBlend()
1883 *
1884 * \param[in] pix 32 bpp
1885 * \param[in] box
1886 * \param[in] spacing spacing between lines; must be > 1
1887 * \param[in] width thickness of box and hash lines
1888 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE, ...
1889 * \param[in] outline 0 to skip drawing box outline
1890 * \param[in] rval, gval, bval
1891 * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
1892 * 0.0 is complete transparency (no effect)
1893 * \return 0 if OK, 1 on error
1894 */
1895 l_ok
1896 pixRenderHashBoxBlend(PIX *pix,
1897 BOX *box,
1898 l_int32 spacing,
1899 l_int32 width,
1900 l_int32 orient,
1901 l_int32 outline,
1902 l_int32 rval,
1903 l_int32 gval,
1904 l_int32 bval,
1905 l_float32 fract)
1906 {
1907 PTA *pta;
1908
1909 if (!pix)
1910 return ERROR_INT("pix not defined", __func__, 1);
1911 if (!box)
1912 return ERROR_INT("box not defined", __func__, 1);
1913 if (spacing <= 1)
1914 return ERROR_INT("spacing not > 1", __func__, 1);
1915 if (width < 1) {
1916 L_WARNING("width < 1; setting to 1\n", __func__);
1917 width = 1;
1918 }
1919 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
1920 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
1921 return ERROR_INT("invalid line orientation", __func__, 1);
1922
1923 pta = generatePtaHashBox(box, spacing, width, orient, outline);
1924 if (!pta)
1925 return ERROR_INT("pta not made", __func__, 1);
1926 pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
1927 ptaDestroy(&pta);
1928 return 0;
1929 }
1930
1931
1932 /*!
1933 * \brief pixRenderHashMaskArb()
1934 *
1935 * \param[in] pix any depth; cmapped ok
1936 * \param[in] pixm 1 bpp clipping mask for hash marks
1937 * \param[in] x,y UL corner of %pixm with respect to %pix
1938 * \param[in] spacing spacing between lines; must be > 1
1939 * \param[in] width thickness of box and hash lines
1940 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
1941 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
1942 * L_NEG_SLOPE_LINE
1943 * \param[in] outline 0 to skip drawing box outline
1944 * \param[in] rval, gval, bval
1945 * \return 0 if OK, 1 on error
1946 * <pre>
1947 * Notes:
1948 * (1) This is an in-place operation that renders hash lines
1949 * through a mask %pixm onto %pix. The mask origin is
1950 * translated by (%x,%y) relative to the origin of %pix.
1951 * </pre>
1952 */
1953 l_ok
1954 pixRenderHashMaskArb(PIX *pix,
1955 PIX *pixm,
1956 l_int32 x,
1957 l_int32 y,
1958 l_int32 spacing,
1959 l_int32 width,
1960 l_int32 orient,
1961 l_int32 outline,
1962 l_int32 rval,
1963 l_int32 gval,
1964 l_int32 bval)
1965 {
1966 l_int32 w, h;
1967 BOX *box1, *box2;
1968 PIX *pix1;
1969 PTA *pta1, *pta2;
1970
1971 if (!pix)
1972 return ERROR_INT("pix not defined", __func__, 1);
1973 if (!pixm || pixGetDepth(pixm) != 1)
1974 return ERROR_INT("pixm not defined or not 1 bpp", __func__, 1);
1975 if (spacing <= 1)
1976 return ERROR_INT("spacing not > 1", __func__, 1);
1977 if (width < 1) {
1978 L_WARNING("width < 1; setting to 1\n", __func__);
1979 width = 1;
1980 }
1981 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
1982 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
1983 return ERROR_INT("invalid line orientation", __func__, 1);
1984
1985 /* Get the points for masked hash lines */
1986 pixGetDimensions(pixm, &w, &h, NULL);
1987 box1 = boxCreate(0, 0, w, h);
1988 pta1 = generatePtaHashBox(box1, spacing, width, orient, outline);
1989 pta2 = ptaCropToMask(pta1, pixm);
1990 boxDestroy(&box1);
1991 ptaDestroy(&pta1);
1992
1993 /* Clip out the region and apply the hash lines */
1994 box2 = boxCreate(x, y, w, h);
1995 pix1 = pixClipRectangle(pix, box2, NULL);
1996 pixRenderPtaArb(pix1, pta2, rval, gval, bval);
1997 ptaDestroy(&pta2);
1998 boxDestroy(&box2);
1999
2000 /* Rasterop the altered rectangle back in place */
2001 pixRasterop(pix, x, y, w, h, PIX_SRC, pix1, 0, 0);
2002 pixDestroy(&pix1);
2003 return 0;
2004 }
2005
2006
2007 /*!
2008 * \brief pixRenderHashBoxa()
2009 *
2010 * \param[in] pix any depth, not cmapped
2011 * \param[in] boxa
2012 * \param[in] spacing spacing between lines; must be > 1
2013 * \param[in] width thickness of box and hash lines
2014 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
2015 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
2016 * L_NEG_SLOPE_LINE
2017 * \param[in] outline 0 to skip drawing box outline
2018 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
2019 * \return 0 if OK, 1 on error
2020 */
2021 l_ok
2022 pixRenderHashBoxa(PIX *pix,
2023 BOXA *boxa,
2024 l_int32 spacing,
2025 l_int32 width,
2026 l_int32 orient,
2027 l_int32 outline,
2028 l_int32 op)
2029 {
2030 PTA *pta;
2031
2032 if (!pix)
2033 return ERROR_INT("pix not defined", __func__, 1);
2034 if (!boxa)
2035 return ERROR_INT("boxa not defined", __func__, 1);
2036 if (spacing <= 1)
2037 return ERROR_INT("spacing not > 1", __func__, 1);
2038 if (width < 1) {
2039 L_WARNING("width < 1; setting to 1\n", __func__);
2040 width = 1;
2041 }
2042 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
2043 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
2044 return ERROR_INT("invalid line orientation", __func__, 1);
2045 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
2046 return ERROR_INT("invalid op", __func__, 1);
2047
2048 pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
2049 if (!pta)
2050 return ERROR_INT("pta not made", __func__, 1);
2051 pixRenderPta(pix, pta, op);
2052 ptaDestroy(&pta);
2053 return 0;
2054 }
2055
2056
2057 /*!
2058 * \brief pixRenderHashBoxaArb()
2059 *
2060 * \param[in] pix any depth; cmapped ok
2061 * \param[in] box
2062 * \param[in] spacing spacing between lines; must be > 1
2063 * \param[in] width thickness of box and hash lines
2064 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
2065 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
2066 * L_NEG_SLOPE_LINE
2067 * \param[in] outline 0 to skip drawing box outline
2068 * \param[in] rval, gval, bval
2069 * \return 0 if OK, 1 on error
2070 */
2071 l_ok
2072 pixRenderHashBoxaArb(PIX *pix,
2073 BOXA *boxa,
2074 l_int32 spacing,
2075 l_int32 width,
2076 l_int32 orient,
2077 l_int32 outline,
2078 l_int32 rval,
2079 l_int32 gval,
2080 l_int32 bval)
2081 {
2082 PTA *pta;
2083
2084 if (!pix)
2085 return ERROR_INT("pix not defined", __func__, 1);
2086 if (!boxa)
2087 return ERROR_INT("boxa not defined", __func__, 1);
2088 if (spacing <= 1)
2089 return ERROR_INT("spacing not > 1", __func__, 1);
2090 if (width < 1) {
2091 L_WARNING("width < 1; setting to 1\n", __func__);
2092 width = 1;
2093 }
2094 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
2095 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
2096 return ERROR_INT("invalid line orientation", __func__, 1);
2097
2098 pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
2099 if (!pta)
2100 return ERROR_INT("pta not made", __func__, 1);
2101 pixRenderPtaArb(pix, pta, rval, gval, bval);
2102 ptaDestroy(&pta);
2103 return 0;
2104 }
2105
2106
2107 /*!
2108 * \brief pixRenderHashBoxaBlend()
2109 *
2110 * \param[in] pix 32 bpp rgb
2111 * \param[in] boxa
2112 * \param[in] spacing spacing between lines; must be > 1
2113 * \param[in] width thickness of box and hash lines
2114 * \param[in] orient orientation of lines: L_HORIZONTAL_LINE,
2115 * L_POS_SLOPE_LINE, L_VERTICAL_LINE,
2116 * L_NEG_SLOPE_LINE
2117 * \param[in] outline 0 to skip drawing box outline
2118 * \param[in] rval, gval, bval
2119 * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
2120 * 0.0 is complete transparency (no effect)
2121 * \return 0 if OK, 1 on error
2122 */
2123 l_ok
2124 pixRenderHashBoxaBlend(PIX *pix,
2125 BOXA *boxa,
2126 l_int32 spacing,
2127 l_int32 width,
2128 l_int32 orient,
2129 l_int32 outline,
2130 l_int32 rval,
2131 l_int32 gval,
2132 l_int32 bval,
2133 l_float32 fract)
2134 {
2135 PTA *pta;
2136
2137 if (!pix)
2138 return ERROR_INT("pix not defined", __func__, 1);
2139 if (!boxa)
2140 return ERROR_INT("boxa not defined", __func__, 1);
2141 if (spacing <= 1)
2142 return ERROR_INT("spacing not > 1", __func__, 1);
2143 if (width < 1) {
2144 L_WARNING("width < 1; setting to 1\n", __func__);
2145 width = 1;
2146 }
2147 if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
2148 orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
2149 return ERROR_INT("invalid line orientation", __func__, 1);
2150
2151 pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
2152 if (!pta)
2153 return ERROR_INT("pta not made", __func__, 1);
2154 pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
2155 ptaDestroy(&pta);
2156 return 0;
2157 }
2158
2159
2160 /*!
2161 * \brief pixRenderPolyline()
2162 *
2163 * \param[in] pix any depth, not cmapped
2164 * \param[in] ptas
2165 * \param[in] width thickness of line
2166 * \param[in] op one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
2167 * \param[in] closeflag 1 to close the contour; 0 otherwise
2168 * \return 0 if OK, 1 on error
2169 *
2170 * <pre>
2171 * Notes:
2172 * This renders a closed contour.
2173 * </pre>
2174 */
2175 l_ok
2176 pixRenderPolyline(PIX *pix,
2177 PTA *ptas,
2178 l_int32 width,
2179 l_int32 op,
2180 l_int32 closeflag)
2181 {
2182 PTA *pta;
2183
2184 if (!pix)
2185 return ERROR_INT("pix not defined", __func__, 1);
2186 if (!ptas)
2187 return ERROR_INT("ptas not defined", __func__, 1);
2188 if (width < 1) {
2189 L_WARNING("width < 1; setting to 1\n", __func__);
2190 width = 1;
2191 }
2192 if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
2193 return ERROR_INT("invalid op", __func__, 1);
2194
2195 if ((pta = generatePtaPolyline(ptas, width, closeflag, 0)) == NULL)
2196 return ERROR_INT("pta not made", __func__, 1);
2197 pixRenderPta(pix, pta, op);
2198 ptaDestroy(&pta);
2199 return 0;
2200 }
2201
2202
2203 /*!
2204 * \brief pixRenderPolylineArb()
2205 *
2206 * \param[in] pix any depth; cmapped ok
2207 * \param[in] ptas
2208 * \param[in] width thickness of line
2209 * \param[in] rval, gval, bval
2210 * \param[in] closeflag 1 to close the contour; 0 otherwise
2211 * \return 0 if OK, 1 on error
2212 *
2213 * <pre>
2214 * Notes:
2215 * This renders a closed contour.
2216 * </pre>
2217 */
2218 l_ok
2219 pixRenderPolylineArb(PIX *pix,
2220 PTA *ptas,
2221 l_int32 width,
2222 l_uint8 rval,
2223 l_uint8 gval,
2224 l_uint8 bval,
2225 l_int32 closeflag)
2226 {
2227 PTA *pta;
2228
2229 if (!pix)
2230 return ERROR_INT("pix not defined", __func__, 1);
2231 if (!ptas)
2232 return ERROR_INT("ptas not defined", __func__, 1);
2233 if (width < 1) {
2234 L_WARNING("width < 1; setting to 1\n", __func__);
2235 width = 1;
2236 }
2237
2238 if ((pta = generatePtaPolyline(ptas, width, closeflag, 0)) == NULL)
2239 return ERROR_INT("pta not made", __func__, 1);
2240 pixRenderPtaArb(pix, pta, rval, gval, bval);
2241 ptaDestroy(&pta);
2242 return 0;
2243 }
2244
2245
2246 /*!
2247 * \brief pixRenderPolylineBlend()
2248 *
2249 * \param[in] pix 32 bpp rgb
2250 * \param[in] ptas
2251 * \param[in] width thickness of line
2252 * \param[in] rval, gval, bval
2253 * \param[in] fract in [0.0 - 1.0]: 1.0 is no transparency;
2254 * 0.0 is complete transparency (no effect)
2255 * \param[in] closeflag 1 to close the contour; 0 otherwise
2256 * \param[in] removedups 1 to remove; 0 otherwise
2257 * \return 0 if OK, 1 on error
2258 */
2259 l_ok
2260 pixRenderPolylineBlend(PIX *pix,
2261 PTA *ptas,
2262 l_int32 width,
2263 l_uint8 rval,
2264 l_uint8 gval,
2265 l_uint8 bval,
2266 l_float32 fract,
2267 l_int32 closeflag,
2268 l_int32 removedups)
2269 {
2270 PTA *pta;
2271
2272 if (!pix)
2273 return ERROR_INT("pix not defined", __func__, 1);
2274 if (!ptas)
2275 return ERROR_INT("ptas not defined", __func__, 1);
2276 if (width < 1) {
2277 L_WARNING("width < 1; setting to 1\n", __func__);
2278 width = 1;
2279 }
2280
2281 if ((pta = generatePtaPolyline(ptas, width, closeflag, removedups)) == NULL)
2282 return ERROR_INT("pta not made", __func__, 1);
2283 pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
2284 ptaDestroy(&pta);
2285 return 0;
2286 }
2287
2288
2289 /*!
2290 * \brief pixRenderGridArb()
2291 *
2292 * \param[in] pix any depth, cmapped ok
2293 * \param[in] nx, ny number of rectangles in each direction
2294 * \param[in] width thickness of grid lines
2295 * \param[in] rval, gval, bval
2296 * \return 0 if OK, 1 on error
2297 */
2298 l_ok
2299 pixRenderGridArb(PIX *pix,
2300 l_int32 nx,
2301 l_int32 ny,
2302 l_int32 width,
2303 l_uint8 rval,
2304 l_uint8 gval,
2305 l_uint8 bval)
2306 {
2307 l_int32 w, h;
2308 PTA *pta;
2309
2310 if (!pix)
2311 return ERROR_INT("pix not defined", __func__, 1);
2312 if (nx < 1 || ny < 1)
2313 return ERROR_INT("nx, ny must be > 0", __func__, 1);
2314 if (width < 1) {
2315 L_WARNING("width < 1; setting to 1\n", __func__);
2316 width = 1;
2317 }
2318
2319 pixGetDimensions(pix, &w, &h, NULL);
2320 if ((pta = generatePtaGrid(w, h, nx, ny, width)) == NULL)
2321 return ERROR_INT("pta not made", __func__, 1);
2322 pixRenderPtaArb(pix, pta, rval, gval, bval);
2323 ptaDestroy(&pta);
2324 return 0;
2325 }
2326
2327
2328 /*!
2329 * \brief pixRenderRandomCmapPtaa()
2330 *
2331 * \param[in] pix 1, 2, 4, 8, 16, 32 bpp
2332 * \param[in] ptaa
2333 * \param[in] polyflag 1 to interpret each Pta as a polyline;
2334 * 0 to simply render the Pta as a set of pixels
2335 * \param[in] width thickness of line; use only for polyline
2336 * \param[in] closeflag 1 to close the contour; 0 otherwise;
2337 * use only for polyline mode
2338 * \return pixd cmapped, 8 bpp or NULL on error
2339 *
2340 * <pre>
2341 * Notes:
2342 * (1) This is a debugging routine, that displays a set of
2343 * pixels, selected by the set of Ptas in a Ptaa,
2344 * in a random color in a pix.
2345 * (2) If %polyflag == 1, each Pta is considered to be a polyline,
2346 * and is rendered using %width and %closeflag. Each polyline
2347 * is rendered in a random color.
2348 * (3) If %polyflag == 0, all points in each Pta are rendered in a
2349 * random color. The %width and %closeflag parameters are ignored.
2350 * (4) The output pix is 8 bpp and colormapped. Up to 254
2351 * different, randomly selected colors, can be used.
2352 * (5) The rendered pixels replace the input pixels. They will
2353 * be clipped silently to the input pix.
2354 * </pre>
2355 */
2356 PIX *
2357 pixRenderRandomCmapPtaa(PIX *pix,
2358 PTAA *ptaa,
2359 l_int32 polyflag,
2360 l_int32 width,
2361 l_int32 closeflag)
2362 {
2363 l_int32 i, n, index, rval, gval, bval;
2364 PIXCMAP *cmap;
2365 PTA *pta, *ptat;
2366 PIX *pixd;
2367
2368 if (!pix)
2369 return (PIX *)ERROR_PTR("pix not defined", __func__, NULL);
2370 if (!ptaa)
2371 return (PIX *)ERROR_PTR("ptaa not defined", __func__, NULL);
2372 if (polyflag != 0 && width < 1) {
2373 L_WARNING("width < 1; setting to 1\n", __func__);
2374 width = 1;
2375 }
2376
2377 pixd = pixConvertTo8(pix, FALSE);
2378 cmap = pixcmapCreateRandom(8, 1, 1);
2379 pixSetColormap(pixd, cmap);
2380
2381 if ((n = ptaaGetCount(ptaa)) == 0)
2382 return pixd;
2383
2384 for (i = 0; i < n; i++) {
2385 index = 1 + (i % 254);
2386 pixcmapGetColor(cmap, index, &rval, &gval, &bval);
2387 pta = ptaaGetPta(ptaa, i, L_CLONE);
2388 if (polyflag)
2389 ptat = generatePtaPolyline(pta, width, closeflag, 0);
2390 else
2391 ptat = ptaClone(pta);
2392 pixRenderPtaArb(pixd, ptat, rval, gval, bval);
2393 ptaDestroy(&pta);
2394 ptaDestroy(&ptat);
2395 }
2396
2397 return pixd;
2398 }
2399
2400
2401
2402 /*------------------------------------------------------------------*
2403 * Rendering and filling of polygons *
2404 *------------------------------------------------------------------*/
2405 /*!
2406 * \brief pixRenderPolygon()
2407 *
2408 * \param[in] ptas of vertices, none repeated
2409 * \param[in] width of polygon outline
2410 * \param[out] pxmin [optional] min x value of input pts
2411 * \param[out] pymin [optional] min y value of input pts
2412 * \return pix 1 bpp, with outline generated, or NULL on error
2413 *
2414 * <pre>
2415 * Notes:
2416 * (1) The pix is the minimum size required to contain the origin
2417 * and the polygon. For example, the max x value of the input
2418 * points is w - 1, where w is the pix width.
2419 * (2) The rendered line is 4-connected, so that an interior or
2420 * exterior 8-c.c. flood fill operation works properly.
2421 * </pre>
2422 */
2423 PIX *
2424 pixRenderPolygon(PTA *ptas,
2425 l_int32 width,
2426 l_int32 *pxmin,
2427 l_int32 *pymin)
2428 {
2429 l_float32 fxmin, fxmax, fymin, fymax;
2430 PIX *pixd;
2431 PTA *pta1, *pta2;
2432
2433 if (pxmin) *pxmin = 0;
2434 if (pymin) *pymin = 0;
2435 if (!ptas)
2436 return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL);
2437
2438 /* Generate a 4-connected polygon line */
2439 if ((pta1 = generatePtaPolyline(ptas, width, 1, 0)) == NULL)
2440 return (PIX *)ERROR_PTR("pta1 not made", __func__, NULL);
2441 if (width < 2)
2442 pta2 = convertPtaLineTo4cc(pta1);
2443 else
2444 pta2 = ptaClone(pta1);
2445
2446 /* Render onto a minimum-sized pix */
2447 ptaGetRange(pta2, &fxmin, &fxmax, &fymin, &fymax);
2448 if (pxmin) *pxmin = (l_int32)(fxmin + 0.5);
2449 if (pymin) *pymin = (l_int32)(fymin + 0.5);
2450 pixd = pixCreate((l_int32)(fxmax + 0.5) + 1, (l_int32)(fymax + 0.5) + 1, 1);
2451 pixRenderPolyline(pixd, pta2, width, L_SET_PIXELS, 1);
2452 ptaDestroy(&pta1);
2453 ptaDestroy(&pta2);
2454 return pixd;
2455 }
2456
2457
2458 /*!
2459 * \brief pixFillPolygon()
2460 *
2461 * \param[in] pixs 1 bpp, with 4-connected polygon outline
2462 * \param[in] pta vertices of the polygon
2463 * \param[in] xmin, ymin min values of vertices of polygon
2464 * \return pixd with outline filled, or NULL on error
2465 *
2466 * <pre>
2467 * Notes:
2468 * (1) This fills the interior of the polygon, returning a
2469 * new pix. It works for both convex and non-convex polygons.
2470 * (2) To generate a filled polygon from %pta:
2471 * PIX *pixt = pixRenderPolygon(pta, 1, &xmin, &ymin);
2472 * PIX *pixd = pixFillPolygon(pixt, pta, xmin, ymin);
2473 * pixDestroy(&pixt);
2474 * </pre>
2475 */
2476 PIX *
2477 pixFillPolygon(PIX *pixs,
2478 PTA *pta,
2479 l_int32 xmin,
2480 l_int32 ymin)
2481 {
2482 l_int32 w, h, i, n, inside, found;
2483 l_int32 *xstart, *xend;
2484 PIX *pixi, *pixd;
2485
2486 if (!pixs || (pixGetDepth(pixs) != 1))
2487 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
2488 if (!pta)
2489 return (PIX *)ERROR_PTR("pta not defined", __func__, NULL);
2490 if (ptaGetCount(pta) < 2)
2491 return (PIX *)ERROR_PTR("pta has < 2 pts", __func__, NULL);
2492
2493 pixGetDimensions(pixs, &w, &h, NULL);
2494 xstart = (l_int32 *)LEPT_CALLOC(L_MAX(1, w / 2), sizeof(l_int32));
2495 xend = (l_int32 *)LEPT_CALLOC(L_MAX(1, w / 2), sizeof(l_int32));
2496 if (!xstart || !xend) {
2497 LEPT_FREE(xstart);
2498 LEPT_FREE(xend);
2499 return (PIX *)ERROR_PTR("xstart and xend not made", __func__, NULL);
2500 }
2501
2502 /* Find a raster with 2 or more black runs. The first background
2503 * pixel after the end of the first run is likely to be inside
2504 * the polygon, and can be used as a seed pixel. */
2505 found = FALSE;
2506 for (i = ymin + 1; i < h; i++) {
2507 pixFindHorizontalRuns(pixs, i, xstart, xend, &n);
2508 if (n > 1) {
2509 ptaPtInsidePolygon(pta, xend[0] + 1, i, &inside);
2510 if (inside) {
2511 found = TRUE;
2512 break;
2513 }
2514 }
2515 }
2516 if (!found) {
2517 L_WARNING("nothing found to fill\n", __func__);
2518 LEPT_FREE(xstart);
2519 LEPT_FREE(xend);
2520 return 0;
2521 }
2522
2523 /* Place the seed pixel in the output image */
2524 pixd = pixCreateTemplate(pixs);
2525 pixSetPixel(pixd, xend[0] + 1, i, 1);
2526
2527 /* Invert pixs to make a filling mask, and fill from the seed */
2528 pixi = pixInvert(NULL, pixs);
2529 pixSeedfillBinary(pixd, pixd, pixi, 4);
2530
2531 /* Add the pixels of the original polygon outline */
2532 pixOr(pixd, pixd, pixs);
2533
2534 pixDestroy(&pixi);
2535 LEPT_FREE(xstart);
2536 LEPT_FREE(xend);
2537 return pixd;
2538 }
2539
2540
2541 /*------------------------------------------------------------------*
2542 * Contour rendering on grayscale images *
2543 *------------------------------------------------------------------*/
2544 /*!
2545 * \brief pixRenderContours()
2546 *
2547 * \param[in] pixs 8 or 16 bpp; no colormap
2548 * \param[in] startval value of lowest contour; must be in [0 ... maxval]
2549 * \param[in] incr increment to next contour; must be > 0
2550 * \param[in] outdepth either 1 or depth of pixs
2551 * \return pixd, or NULL on error
2552 *
2553 * <pre>
2554 * Notes:
2555 * (1) The output can be either 1 bpp, showing just the contour
2556 * lines, or a copy of the input pixs with the contour lines
2557 * superposed.
2558 * </pre>
2559 */
2560 PIX *
2561 pixRenderContours(PIX *pixs,
2562 l_int32 startval,
2563 l_int32 incr,
2564 l_int32 outdepth)
2565 {
2566 l_int32 w, h, d, maxval, wpls, wpld, i, j, val, test;
2567 l_uint32 *datas, *datad, *lines, *lined;
2568 PIX *pixd;
2569
2570 if (!pixs)
2571 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2572 if (pixGetColormap(pixs))
2573 return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL);
2574 pixGetDimensions(pixs, &w, &h, &d);
2575 if (d != 8 && d != 16)
2576 return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", __func__, NULL);
2577 if (outdepth != 1 && outdepth != d) {
2578 L_WARNING("invalid outdepth; setting to 1\n", __func__);
2579 outdepth = 1;
2580 }
2581 maxval = (1 << d) - 1;
2582 if (startval < 0 || startval > maxval)
2583 return (PIX *)ERROR_PTR("startval not in [0 ... maxval]",
2584 __func__, NULL);
2585 if (incr < 1)
2586 return (PIX *)ERROR_PTR("incr < 1", __func__, NULL);
2587
2588 if (outdepth == d)
2589 pixd = pixCopy(NULL, pixs);
2590 else
2591 pixd = pixCreate(w, h, 1);
2592
2593 pixCopyResolution(pixd, pixs);
2594 datad = pixGetData(pixd);
2595 wpld = pixGetWpl(pixd);
2596 datas = pixGetData(pixs);
2597 wpls = pixGetWpl(pixs);
2598
2599 switch (d)
2600 {
2601 case 8:
2602 if (outdepth == 1) {
2603 for (i = 0; i < h; i++) {
2604 lines = datas + i * wpls;
2605 lined = datad + i * wpld;
2606 for (j = 0; j < w; j++) {
2607 val = GET_DATA_BYTE(lines, j);
2608 if (val < startval)
2609 continue;
2610 test = (val - startval) % incr;
2611 if (!test)
2612 SET_DATA_BIT(lined, j);
2613 }
2614 }
2615 } else { /* outdepth == d */
2616 for (i = 0; i < h; i++) {
2617 lines = datas + i * wpls;
2618 lined = datad + i * wpld;
2619 for (j = 0; j < w; j++) {
2620 val = GET_DATA_BYTE(lines, j);
2621 if (val < startval)
2622 continue;
2623 test = (val - startval) % incr;
2624 if (!test)
2625 SET_DATA_BYTE(lined, j, 0);
2626 }
2627 }
2628 }
2629 break;
2630
2631 case 16:
2632 if (outdepth == 1) {
2633 for (i = 0; i < h; i++) {
2634 lines = datas + i * wpls;
2635 lined = datad + i * wpld;
2636 for (j = 0; j < w; j++) {
2637 val = GET_DATA_TWO_BYTES(lines, j);
2638 if (val < startval)
2639 continue;
2640 test = (val - startval) % incr;
2641 if (!test)
2642 SET_DATA_BIT(lined, j);
2643 }
2644 }
2645 } else { /* outdepth == d */
2646 for (i = 0; i < h; i++) {
2647 lines = datas + i * wpls;
2648 lined = datad + i * wpld;
2649 for (j = 0; j < w; j++) {
2650 val = GET_DATA_TWO_BYTES(lines, j);
2651 if (val < startval)
2652 continue;
2653 test = (val - startval) % incr;
2654 if (!test)
2655 SET_DATA_TWO_BYTES(lined, j, 0);
2656 }
2657 }
2658 }
2659 break;
2660
2661 default:
2662 return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", __func__, NULL);
2663 }
2664
2665 return pixd;
2666 }
2667
2668
2669 /*!
2670 * \brief fpixAutoRenderContours()
2671 *
2672 * \param[in] fpix
2673 * \param[in] ncontours in [2 ... 500]; typically about 50
2674 * \return pixd 8 bpp, or NULL on error
2675 *
2676 * <pre>
2677 * Notes:
2678 * (1) The increment is set to get approximately %ncontours.
2679 * (2) The proximity to the target value for contour display
2680 * is set to 0.15.
2681 * (3) Negative values are rendered in red; positive values as black.
2682 * </pre>
2683 */
2684 PIX *
2685 fpixAutoRenderContours(FPIX *fpix,
2686 l_int32 ncontours)
2687 {
2688 l_float32 minval, maxval, incr;
2689
2690 if (!fpix)
2691 return (PIX *)ERROR_PTR("fpix not defined", __func__, NULL);
2692 if (ncontours < 2 || ncontours > 500)
2693 return (PIX *)ERROR_PTR("ncontours < 2 or > 500", __func__, NULL);
2694
2695 fpixGetMin(fpix, &minval, NULL, NULL);
2696 fpixGetMax(fpix, &maxval, NULL, NULL);
2697 if (minval == maxval)
2698 return (PIX *)ERROR_PTR("all values in fpix are equal", __func__, NULL);
2699 incr = (maxval - minval) / ((l_float32)ncontours - 1);
2700 return fpixRenderContours(fpix, incr, 0.15f);
2701 }
2702
2703
2704 /*!
2705 * \brief fpixRenderContours()
2706 *
2707 * \param[in] fpixs
2708 * \param[in] incr increment between contours; must be > 0.0
2709 * \param[in] proxim required proximity to target value; default 0.15
2710 * \return pixd 8 bpp, or NULL on error
2711 *
2712 * <pre>
2713 * Notes:
2714 * (1) Values are displayed when val/incr is within +-proxim
2715 * to an integer. The default value is 0.15; smaller values
2716 * result in thinner contour lines.
2717 * (2) Negative values are rendered in red; positive values as black.
2718 * </pre>
2719 */
2720 PIX *
2721 fpixRenderContours(FPIX *fpixs,
2722 l_float32 incr,
2723 l_float32 proxim)
2724 {
2725 l_int32 i, j, w, h, wpls, wpld;
2726 l_float32 val, invincr, finter, above, below, diff;
2727 l_uint32 *datad, *lined;
2728 l_float32 *datas, *lines;
2729 PIX *pixd;
2730 PIXCMAP *cmap;
2731
2732 if (!fpixs)
2733 return (PIX *)ERROR_PTR("fpixs not defined", __func__, NULL);
2734 if (incr <= 0.0)
2735 return (PIX *)ERROR_PTR("incr <= 0.0", __func__, NULL);
2736 if (proxim <= 0.0)
2737 proxim = 0.15f; /* default */
2738
2739 fpixGetDimensions(fpixs, &w, &h);
2740 if ((pixd = pixCreate(w, h, 8)) == NULL)
2741 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2742 cmap = pixcmapCreate(8);
2743 pixSetColormap(pixd, cmap);
2744 pixcmapAddColor(cmap, 255, 255, 255); /* white */
2745 pixcmapAddColor(cmap, 0, 0, 0); /* black */
2746 pixcmapAddColor(cmap, 255, 0, 0); /* red */
2747
2748 datas = fpixGetData(fpixs);
2749 wpls = fpixGetWpl(fpixs);
2750 datad = pixGetData(pixd);
2751 wpld = pixGetWpl(pixd);
2752 invincr = 1.0 / incr;
2753 for (i = 0; i < h; i++) {
2754 lines = datas + i * wpls;
2755 lined = datad + i * wpld;
2756 for (j = 0; j < w; j++) {
2757 val = lines[j];
2758 finter = invincr * val;
2759 above = finter - floorf(finter);
2760 below = ceilf(finter) - finter;
2761 diff = L_MIN(above, below);
2762 if (diff <= proxim) {
2763 if (val < 0.0)
2764 SET_DATA_BYTE(lined, j, 2);
2765 else
2766 SET_DATA_BYTE(lined, j, 1);
2767 }
2768 }
2769 }
2770
2771 return pixd;
2772 }
2773
2774
2775 /*------------------------------------------------------------------*
2776 * Boundary pt generation on 1 bpp images *
2777 *------------------------------------------------------------------*/
2778 /*!
2779 * \brief pixGeneratePtaBoundary()
2780 *
2781 * \param[in] pixs 1 bpp
2782 * \param[in] width of boundary line
2783 * \return pta, or NULL on error
2784 *
2785 * <pre>
2786 * Notes:
2787 * (1) Similar to ptaGetBoundaryPixels(), except here:
2788 * * we only get pixels in the foreground
2789 * * we can have a "line" width greater than 1 pixel.
2790 * (2) Once generated, this can be applied to a random 1 bpp image
2791 * to add a color boundary as follows:
2792 * Pta *pta = pixGeneratePtaBoundary(pixs, width);
2793 * Pix *pix1 = pixConvert1To8Cmap(pixs);
2794 * pixRenderPtaArb(pix1, pta, rval, gval, bval);
2795 * </pre>
2796 */
2797 PTA *
2798 pixGeneratePtaBoundary(PIX *pixs,
2799 l_int32 width)
2800 {
2801 PIX *pix1;
2802 PTA *pta;
2803
2804 if (!pixs || pixGetDepth(pixs) != 1)
2805 return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
2806 if (width < 1) {
2807 L_WARNING("width < 1; setting to 1\n", __func__);
2808 width = 1;
2809 }
2810
2811 pix1 = pixErodeBrick(NULL, pixs, 2 * width + 1, 2 * width + 1);
2812 pixXor(pix1, pix1, pixs);
2813 pta = ptaGetPixelsFromPix(pix1, NULL);
2814 pixDestroy(&pix1);
2815 return pta;
2816 }