diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/leptonica/src/graphics.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,2816 @@
+/*====================================================================*
+ -  Copyright (C) 2001 Leptonica.  All rights reserved.
+ -
+ -  Redistribution and use in source and binary forms, with or without
+ -  modification, are permitted provided that the following conditions
+ -  are met:
+ -  1. Redistributions of source code must retain the above copyright
+ -     notice, this list of conditions and the following disclaimer.
+ -  2. Redistributions in binary form must reproduce the above
+ -     copyright notice, this list of conditions and the following
+ -     disclaimer in the documentation and/or other materials
+ -     provided with the distribution.
+ -
+ -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
+ -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file graphics.c
+ * <pre>
+ *
+ *      Pta generation for arbitrary shapes built with lines
+ *          PTA        *generatePtaLine()
+ *          PTA        *generatePtaWideLine()
+ *          PTA        *generatePtaBox()
+ *          PTA        *generatePtaBoxa()
+ *          PTA        *generatePtaHashBox()
+ *          PTA        *generatePtaHashBoxa()
+ *          PTAA       *generatePtaaBoxa()
+ *          PTAA       *generatePtaaHashBoxa()
+ *          PTA        *generatePtaPolyline()
+ *          PTA        *generatePtaGrid()
+ *          PTA        *convertPtaLineTo4cc()
+ *          PTA        *generatePtaFilledCircle()
+ *          PTA        *generatePtaFilledSquare()
+ *          PTA        *generatePtaLineFromPt()
+ *          l_int32     locatePtRadially()
+ *
+ *      Rendering function plots directly on images
+ *          l_int32     pixRenderPlotFromNuma()
+ *          l_int32     pixRenderPlotFromNumaGen()
+ *          PTA        *makePlotPtaFromNuma()
+ *          PTA        *makePlotPtaFromNumaGen()
+ *
+ *      Pta rendering
+ *          l_int32     pixRenderPta()
+ *          l_int32     pixRenderPtaArb()
+ *          l_int32     pixRenderPtaBlend()
+ *
+ *      Rendering of arbitrary shapes built with lines
+ *          l_int32     pixRenderLine()
+ *          l_int32     pixRenderLineArb()
+ *          l_int32     pixRenderLineBlend()
+ *
+ *          l_int32     pixRenderBox()
+ *          l_int32     pixRenderBoxArb()
+ *          l_int32     pixRenderBoxBlend()
+ *
+ *          l_int32     pixRenderBoxa()
+ *          l_int32     pixRenderBoxaArb()
+ *          l_int32     pixRenderBoxaBlend()
+ *
+ *          l_int32     pixRenderHashBox()
+ *          l_int32     pixRenderHashBoxArb()
+ *          l_int32     pixRenderHashBoxBlend()
+ *          l_int32     pixRenderHashMaskArb()
+ *
+ *          l_int32     pixRenderHashBoxa()
+ *          l_int32     pixRenderHashBoxaArb()
+ *          l_int32     pixRenderHashBoxaBlend()
+ *
+ *          l_int32     pixRenderPolyline()
+ *          l_int32     pixRenderPolylineArb()
+ *          l_int32     pixRenderPolylineBlend()
+ *
+ *          l_int32     pixRenderGrid()
+ *
+ *          l_int32     pixRenderRandomCmapPtaa()
+ *
+ *      Rendering and filling of polygons
+ *          PIX        *pixRenderPolygon()
+ *          PIX        *pixFillPolygon()
+ *
+ *      Contour rendering on grayscale images
+ *          PIX        *pixRenderContours()
+ *          PIX        *fpixAutoRenderContours()
+ *          PIX        *fpixRenderContours()
+ *
+ *      Boundary pt generation on 1 bpp images
+ *          PTA        *pixGeneratePtaBoundary()
+ *
+ *  The line rendering functions are relatively crude, but they
+ *  get the job done for most simple situations.  We use the pta
+ *  (array of points) as an intermediate data structure.  For example,
+ *  to render a line we first generate a pta.
+ *
+ *  Some rendering functions come in sets of three.  For example
+ *       pixRenderLine() -- render on 1 bpp pix
+ *       pixRenderLineArb() -- render on 32 bpp pix with arbitrary (r,g,b)
+ *       pixRenderLineBlend() -- render on 32 bpp pix, blending the
+ *               (r,g,b) graphic object with the underlying rgb pixels.
+ *
+ *  There are also procedures for plotting a function, computed
+ *  from the row or column pixels, directly on the image.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/*------------------------------------------------------------------*
+ *        Pta generation for arbitrary shapes built with lines      *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   generatePtaLine()
+ *
+ * \param[in]    x1, y1    end point 1
+ * \param[in]    x2, y2    end point 2
+ * \return  pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Uses Bresenham line drawing, which results in an 8-connected line.
+ * </pre>
+ */
+PTA  *
+generatePtaLine(l_int32  x1,
+                l_int32  y1,
+                l_int32  x2,
+                l_int32  y2)
+{
+l_int32    npts, diff, getyofx, sign, i, x, y;
+l_float32  slope;
+PTA       *pta;
+
+        /* Generate line parameters */
+    if (x1 == x2 && y1 == y2) {  /* same point */
+        getyofx = TRUE;
+        npts = 1;
+    } else if (L_ABS(x2 - x1) >= L_ABS(y2 - y1)) {
+        getyofx = TRUE;
+        npts = L_ABS(x2 - x1) + 1;
+        diff = x2 - x1;
+        sign = L_SIGN(x2 - x1);
+        slope = (l_float32)(sign * (y2 - y1)) / (l_float32)diff;
+    } else {
+        getyofx = FALSE;
+        npts = L_ABS(y2 - y1) + 1;
+        diff = y2 - y1;
+        sign = L_SIGN(y2 - y1);
+        slope = (l_float32)(sign * (x2 - x1)) / (l_float32)diff;
+    }
+
+    if ((pta = ptaCreate(npts)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", __func__, NULL);
+
+    if (npts == 1) {  /* degenerate case */
+        ptaAddPt(pta, x1, y1);
+        return pta;
+    }
+
+        /* Generate the set of points */
+    if (getyofx) {  /* y = y(x) */
+        for (i = 0; i < npts; i++) {
+            x = x1 + sign * i;
+            y = (l_int32)(y1 + (l_float32)i * slope + 0.5);
+            ptaAddPt(pta, x, y);
+        }
+    } else {   /* x = x(y) */
+        for (i = 0; i < npts; i++) {
+            x = (l_int32)(x1 + (l_float32)i * slope + 0.5);
+            y = y1 + sign * i;
+            ptaAddPt(pta, x, y);
+        }
+    }
+
+    return pta;
+}
+
+
+/*!
+ * \brief   generatePtaWideLine()
+ *
+ * \param[in]    x1, y1     end point 1
+ * \param[in]    x2, y2     end point 2
+ * \param[in]    width
+ * \return  ptaj, or NULL on error
+ */
+PTA  *
+generatePtaWideLine(l_int32  x1,
+                    l_int32  y1,
+                    l_int32  x2,
+                    l_int32  y2,
+                    l_int32  width)
+{
+l_int32  i, x1a, x2a, y1a, y2a;
+PTA     *pta, *ptaj;
+
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((ptaj = generatePtaLine(x1, y1, x2, y2)) == NULL)
+        return (PTA *)ERROR_PTR("ptaj not made", __func__, NULL);
+    if (width == 1)
+        return ptaj;
+
+        /* width > 1; estimate line direction & join */
+    if (L_ABS(x1 - x2) > L_ABS(y1 - y2)) {  /* "horizontal" line  */
+        for (i = 1; i < width; i++) {
+            if ((i & 1) == 1) {   /* place above */
+                y1a = y1 - (i + 1) / 2;
+                y2a = y2 - (i + 1) / 2;
+            } else {  /* place below */
+                y1a = y1 + (i + 1) / 2;
+                y2a = y2 + (i + 1) / 2;
+            }
+            if ((pta = generatePtaLine(x1, y1a, x2, y2a)) != NULL) {
+                ptaJoin(ptaj, pta, 0, -1);
+                ptaDestroy(&pta);
+            }
+        }
+    } else  {  /* "vertical" line  */
+        for (i = 1; i < width; i++) {
+            if ((i & 1) == 1) {   /* place to left */
+                x1a = x1 - (i + 1) / 2;
+                x2a = x2 - (i + 1) / 2;
+            } else {  /* place to right */
+                x1a = x1 + (i + 1) / 2;
+                x2a = x2 + (i + 1) / 2;
+            }
+            if ((pta = generatePtaLine(x1a, y1, x2a, y2)) != NULL) {
+                ptaJoin(ptaj, pta, 0, -1);
+                ptaDestroy(&pta);
+            }
+        }
+    }
+
+    return ptaj;
+}
+
+
+/*!
+ * \brief   generatePtaBox()
+ *
+ * \param[in]    box
+ * \param[in]    width    of line
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Because the box is constructed so that we don't have any
+ *          overlapping lines, there is no need to remove duplicates.
+ * </pre>
+ */
+PTA  *
+generatePtaBox(BOX     *box,
+               l_int32  width)
+{
+l_int32  x, y, w, h;
+PTA     *ptad, *pta;
+
+    if (!box)
+        return (PTA *)ERROR_PTR("box not defined", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+        /* Generate line points and add them to the pta. */
+    boxGetGeometry(box, &x, &y, &w, &h);
+    if (w == 0 || h == 0)
+        return (PTA *)ERROR_PTR("box has w = 0 or h = 0", __func__, NULL);
+    ptad = ptaCreate(0);
+    if ((width & 1) == 1) {   /* odd width */
+        pta = generatePtaWideLine(x - width / 2, y,
+                                  x + w - 1 + width / 2, y, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 1, y + 1 + width / 2,
+                                  x + w - 1, y + h - 2 - width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 1 + width / 2, y + h - 1,
+                                  x - width / 2, y + h - 1, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x, y + h - 2 - width / 2,
+                                  x, y + 1 + width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+    } else {   /* even width */
+        pta = generatePtaWideLine(x - width / 2, y,
+                                  x + w - 2 + width / 2, y, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 1, y + 0 + width / 2,
+                                  x + w - 1, y + h - 2 - width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 2 + width / 2, y + h - 1,
+                                  x - width / 2, y + h - 1, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x, y + h - 2 - width / 2,
+                                  x, y + 0 + width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ * \brief   generatePtaBoxa()
+ *
+ * \param[in]    boxa
+ * \param[in]    width
+ * \param[in]    removedups    1 to remove, 0 to leave
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) If %boxa has overlapping boxes, and if blending will
+ *          be used to give a transparent effect, transparency
+ *          artifacts at line intersections can be removed using
+ *          %removedups = 1.
+ * </pre>
+ */
+PTA  *
+generatePtaBoxa(BOXA    *boxa,
+                l_int32  width,
+                l_int32  removedups)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *ptad, *ptat, *pta;
+
+    if (!boxa)
+        return (PTA *)ERROR_PTR("boxa not defined", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    n = boxaGetCount(boxa);
+    ptat = ptaCreate(0);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pta = generatePtaBox(box, width);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+        boxDestroy(&box);
+    }
+
+    if (removedups)
+        ptaRemoveDupsByAset(ptat, &ptad);
+    else
+        ptad = ptaClone(ptat);
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ * \brief   generatePtaHashBox()
+ *
+ * \param[in]    box
+ * \param[in]    spacing    spacing between lines; must be > 1
+ * \param[in]    width      of line
+ * \param[in]    orient     orientation of lines: L_HORIZONTAL_LINE,
+ *                             L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                             L_NEG_SLOPE_LINE
+ * \param[in]    outline    0 to skip drawing box outline
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The orientation takes on one of 4 orientations (horiz, vertical,
+ *          slope +1, slope -1).
+ *      (2) The full outline is also drawn if %outline = 1.
+ * </pre>
+ */
+PTA  *
+generatePtaHashBox(BOX     *box,
+                   l_int32  spacing,
+                   l_int32  width,
+                   l_int32  orient,
+                   l_int32  outline)
+{
+l_int32  bx, by, bh, bw, x, y, x1, y1, x2, y2, i, n, npts;
+PTA     *ptad, *pta;
+
+    if (!box)
+        return (PTA *)ERROR_PTR("box not defined", __func__, NULL);
+    if (spacing <= 1)
+        return (PTA *)ERROR_PTR("spacing not > 1", __func__, NULL);
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return (PTA *)ERROR_PTR("invalid line orientation", __func__, NULL);
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+    if (bw == 0 || bh == 0)
+        return (PTA *)ERROR_PTR("box has bw = 0 or bh = 0", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+        /* Generate line points and add them to the pta. */
+    ptad = ptaCreate(0);
+    if (outline) {
+        pta = generatePtaBox(box, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+    }
+    if (orient == L_HORIZONTAL_LINE) {
+        n = 1 + bh / spacing;
+        for (i = 0; i < n; i++) {
+            y = by + (i * (bh - 1)) / (n - 1);
+            pta = generatePtaWideLine(bx, y, bx + bw - 1, y, width);
+            ptaJoin(ptad, pta, 0, -1);
+            ptaDestroy(&pta);
+        }
+    } else if (orient == L_VERTICAL_LINE) {
+        n = 1 + bw / spacing;
+        for (i = 0; i < n; i++) {
+            x = bx + (i * (bw - 1)) / (n - 1);
+            pta = generatePtaWideLine(x, by, x, by + bh - 1, width);
+            ptaJoin(ptad, pta, 0, -1);
+            ptaDestroy(&pta);
+        }
+    } else if (orient == L_POS_SLOPE_LINE) {
+        n = 2 + (l_int32)((bw + bh) / (1.4 * spacing));
+        for (i = 0; i < n; i++) {
+            x = (l_int32)(bx + (i + 0.5) * 1.4 * spacing);
+            boxIntersectByLine(box, x, by - 1, 1.0, &x1, &y1, &x2, &y2, &npts);
+            if (npts == 2) {
+                pta = generatePtaWideLine(x1, y1, x2, y2, width);
+                ptaJoin(ptad, pta, 0, -1);
+                ptaDestroy(&pta);
+            }
+        }
+    } else {  /* orient == L_NEG_SLOPE_LINE */
+        n = 2 + (l_int32)((bw + bh) / (1.4 * spacing));
+        for (i = 0; i < n; i++) {
+            x = (l_int32)(bx - bh + (i + 0.5) * 1.4 * spacing);
+            boxIntersectByLine(box, x, by - 1, -1.0, &x1, &y1, &x2, &y2, &npts);
+            if (npts == 2) {
+                pta = generatePtaWideLine(x1, y1, x2, y2, width);
+                ptaJoin(ptad, pta, 0, -1);
+                ptaDestroy(&pta);
+            }
+        }
+    }
+
+    return ptad;
+}
+
+
+/*!
+ * \brief   generatePtaHashBoxa()
+ *
+ * \param[in]    boxa
+ * \param[in]    spacing     spacing between lines; must be > 1
+ * \param[in]    width       of line
+ * \param[in]    orient      orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in]    orient      orientation of lines: L_HORIZONTAL_LINE,
+ *                              L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                              L_NEG_SLOPE_LINE
+ * \param[in]    outline     0 to skip drawing box outline
+ * \param[in]    removedups  1 to remove, 0 to leave
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The orientation takes on one of 4 orientations (horiz, vertical,
+ *          slope +1, slope -1).
+ *      (2) The full outline is also drawn if %outline = 1.
+ *      (3) If the boxa has overlapping boxes, and if blending will
+ *          be used to give a transparent effect, transparency
+ *          artifacts at line intersections can be removed using
+ *          %removedups = 1.
+ * </pre>
+ */
+PTA  *
+generatePtaHashBoxa(BOXA    *boxa,
+                    l_int32  spacing,
+                    l_int32  width,
+                    l_int32  orient,
+                    l_int32  outline,
+                    l_int32  removedups)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *ptad, *ptat, *pta;
+
+    if (!boxa)
+        return (PTA *)ERROR_PTR("boxa not defined", __func__, NULL);
+    if (spacing <= 1)
+        return (PTA *)ERROR_PTR("spacing not > 1", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return (PTA *)ERROR_PTR("invalid line orientation", __func__, NULL);
+
+    n = boxaGetCount(boxa);
+    ptat = ptaCreate(0);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pta = generatePtaHashBox(box, spacing, width, orient, outline);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+        boxDestroy(&box);
+    }
+
+    if (removedups)
+        ptaRemoveDupsByAset(ptat, &ptad);
+    else
+        ptad = ptaClone(ptat);
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ * \brief   generatePtaaBoxa()
+ *
+ * \param[in]    boxa
+ * \return  ptaa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This generates a pta of the four corners for each box in
+ *          the boxa.
+ *      (2) Each of these pta can be rendered onto a pix with random colors,
+ *          by using pixRenderRandomCmapPtaa() with closeflag = 1.
+ * </pre>
+ */
+PTAA  *
+generatePtaaBoxa(BOXA  *boxa)
+{
+l_int32  i, n, x, y, w, h;
+BOX     *box;
+PTA     *pta;
+PTAA    *ptaa;
+
+    if (!boxa)
+        return (PTAA *)ERROR_PTR("boxa not defined", __func__, NULL);
+
+    n = boxaGetCount(boxa);
+    ptaa = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        pta = ptaCreate(4);
+        ptaAddPt(pta, x, y);
+        ptaAddPt(pta, x + w - 1, y);
+        ptaAddPt(pta, x + w - 1, y + h - 1);
+        ptaAddPt(pta, x, y + h - 1);
+        ptaaAddPta(ptaa, pta, L_INSERT);
+        boxDestroy(&box);
+    }
+
+    return ptaa;
+}
+
+
+/*!
+ * \brief   generatePtaaHashBoxa()
+ *
+ * \param[in]    boxa
+ * \param[in]    spacing     spacing between hash lines; must be > 1
+ * \param[in]    width       hash line width
+ * \param[in]    orient      orientation of lines: L_HORIZONTAL_LINE,
+ *                              L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                              L_NEG_SLOPE_LINE
+ * \param[in]    outline     0 to skip drawing box outline
+ * \return  ptaa, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The orientation takes on one of 4 orientations (horiz, vertical,
+ *          slope +1, slope -1).
+ *      (2) The full outline is also drawn if %outline = 1.
+ *      (3) Each of these pta can be rendered onto a pix with random colors,
+ *          by using pixRenderRandomCmapPtaa() with closeflag = 1.
+ *
+ * </pre>
+ */
+PTAA *
+generatePtaaHashBoxa(BOXA    *boxa,
+                     l_int32  spacing,
+                     l_int32  width,
+                     l_int32  orient,
+                     l_int32  outline)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *pta;
+PTAA    *ptaa;
+
+    if (!boxa)
+        return (PTAA *)ERROR_PTR("boxa not defined", __func__, NULL);
+    if (spacing <= 1)
+        return (PTAA *)ERROR_PTR("spacing not > 1", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return (PTAA *)ERROR_PTR("invalid line orientation", __func__, NULL);
+
+    n = boxaGetCount(boxa);
+    ptaa = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pta = generatePtaHashBox(box, spacing, width, orient, outline);
+        ptaaAddPta(ptaa, pta, L_INSERT);
+        boxDestroy(&box);
+    }
+
+    return ptaa;
+}
+
+
+/*!
+ * \brief   generatePtaPolyline()
+ *
+ * \param[in]    ptas         vertices of polyline
+ * \param[in]    width
+ * \param[in]    closeflag    1 to close the contour; 0 otherwise
+ * \param[in]    removedups   1 to remove, 0 to leave
+ * \return  ptad, or NULL on error
+ */
+PTA *
+generatePtaPolyline(PTA     *ptas,
+                    l_int32  width,
+                    l_int32  closeflag,
+                    l_int32  removedups)
+{
+l_int32  i, n, x1, y1, x2, y2;
+PTA     *ptad, *ptat, *pta;
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    n = ptaGetCount(ptas);
+    ptat = ptaCreate(0);
+    if (n < 2)  /* nothing to do */
+        return ptat;
+
+    ptaGetIPt(ptas, 0, &x1, &y1);
+    for (i = 1; i < n; i++) {
+        ptaGetIPt(ptas, i, &x2, &y2);
+        pta = generatePtaWideLine(x1, y1, x2, y2, width);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+        x1 = x2;
+        y1 = y2;
+    }
+
+    if (closeflag) {
+        ptaGetIPt(ptas, 0, &x2, &y2);
+        pta = generatePtaWideLine(x1, y1, x2, y2, width);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+    }
+
+    if (removedups)
+        ptaRemoveDupsByAset(ptat, &ptad);
+    else
+        ptad = ptaClone(ptat);
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ * \brief   generatePtaGrid()
+ *
+ * \param[in]    w, h       of region where grid will be displayed
+ * \param[in]    nx, ny     number of rectangles in each direction in grid
+ * \param[in]    width      of rendered lines
+ * \return  ptad, or NULL on error
+ */
+PTA  *
+generatePtaGrid(l_int32  w,
+                l_int32  h,
+                l_int32  nx,
+                l_int32  ny,
+                l_int32  width)
+{
+l_int32  i, j, bx, by, x1, x2, y1, y2;
+BOX     *box;
+BOXA    *boxa;
+PTA     *pta;
+
+    if (nx < 1 || ny < 1)
+        return (PTA *)ERROR_PTR("nx and ny must be > 0", __func__, NULL);
+    if (w < 2 * nx || h < 2 * ny)
+        return (PTA *)ERROR_PTR("w and/or h too small", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    boxa = boxaCreate(nx * ny);
+    bx = (w + nx - 1) / nx;
+    by = (h + ny - 1) / ny;
+    for (i = 0; i < ny; i++) {
+        y1 = by * i;
+        y2 = L_MIN(y1 + by, h - 1);
+        for (j = 0; j < nx; j++) {
+            x1 = bx * j;
+            x2 = L_MIN(x1 + bx, w - 1);
+            box = boxCreate(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+    }
+
+    pta = generatePtaBoxa(boxa, width, 1);
+    boxaDestroy(&boxa);
+    return pta;
+}
+
+
+/*!
+ * \brief   convertPtaLineTo4cc()
+ *
+ * \param[in]    ptas    8-connected line of points
+ * \return  ptad 4-connected line, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) When a polyline is generated with width = 1, the resulting
+ *          line is not 4-connected in general.  This function adds
+ *          points as necessary to convert the line to 4-cconnected.
+ *          It is useful when rendering 1 bpp on a pix.
+ *      (2) Do not use this for lines generated with width > 1.
+ * </pre>
+ */
+PTA *
+convertPtaLineTo4cc(PTA  *ptas)
+{
+l_int32  i, n, x, y, xp, yp;
+PTA     *ptad;
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL);
+
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(n);
+    ptaGetIPt(ptas, 0, &xp, &yp);
+    ptaAddPt(ptad, xp, yp);
+    for (i = 1; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        if (x != xp && y != yp)  /* diagonal */
+            ptaAddPt(ptad, x, yp);
+        ptaAddPt(ptad, x, y);
+        xp = x;
+        yp = y;
+    }
+
+    return ptad;
+}
+
+
+/*!
+ * \brief   generatePtaFilledCircle()
+ *
+ * \param[in]    radius
+ * \return  pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The circle is has diameter = 2 * radius + 1.
+ *      (2) It is located with the center of the circle at the
+ *          point (%radius, %radius).
+ *      (3) Consequently, it typically must be translated if
+ *          it is to represent a set of pixels in an image.
+ * </pre>
+ */
+PTA *
+generatePtaFilledCircle(l_int32  radius)
+{
+l_int32    x, y;
+l_float32  radthresh, sqdist;
+PTA       *pta;
+
+    if (radius < 1)
+        return (PTA *)ERROR_PTR("radius must be >= 1", __func__, NULL);
+
+    pta = ptaCreate(0);
+    radthresh = (radius + 0.5) * (radius + 0.5);
+    for (y = 0; y <= 2 * radius; y++) {
+        for (x = 0; x <= 2 * radius; x++) {
+            sqdist = (l_float32)((y - radius) * (y - radius) +
+                                 (x - radius) * (x - radius));
+            if (sqdist <= radthresh)
+                ptaAddPt(pta, x, y);
+        }
+    }
+
+    return pta;
+}
+
+
+/*!
+ * \brief   generatePtaFilledSquare()
+ *
+ * \param[in]    side
+ * \return  pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The center of the square can be chosen to be at
+ *          (side / 2, side / 2).  It must be translated by this amount
+ *          when used for replication.
+ * </pre>
+ */
+PTA *
+generatePtaFilledSquare(l_int32  side)
+{
+l_int32  x, y;
+PTA     *pta;
+
+    if (side < 1)
+        return (PTA *)ERROR_PTR("side must be > 0", __func__, NULL);
+
+    pta = ptaCreate(0);
+    for (y = 0; y < side; y++)
+        for (x = 0; x < side; x++)
+            ptaAddPt(pta, x, y);
+
+    return pta;
+}
+
+
+/*!
+ * \brief   generatePtaLineFromPt()
+ *
+ * \param[in]    x, y      point of origination
+ * \param[in]    length    of line, including starting point
+ * \param[in]    radang    angle in radians, CW from horizontal
+ * \return  pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) %length of the line is 1 greater than the distance
+ *          used in locatePtRadially().  Example: a distance of 1
+ *          gives rise to a length of 2.
+ * </pre>
+ */
+PTA *
+generatePtaLineFromPt(l_int32    x,
+                      l_int32    y,
+                      l_float64  length,
+                      l_float64  radang)
+{
+l_int32  x2, y2;  /* the point at the other end of the line */
+
+    x2 = x + (l_int32)((length - 1.0) * cos(radang));
+    y2 = y + (l_int32)((length - 1.0) * sin(radang));
+    return generatePtaLine(x, y, x2, y2);
+}
+
+
+/*!
+ * \brief   locatePtRadially()
+ *
+ * \param[in]    xr, yr    reference point
+ * \param[in]    radang    angle in radians, CW from horizontal
+ * \param[in]    dist      distance of point from reference point along
+ *                         line given by the specified angle
+ * \param[out]   px, py    location of point
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+locatePtRadially(l_int32     xr,
+                 l_int32     yr,
+                 l_float64   dist,
+                 l_float64   radang,
+                 l_float64  *px,
+                 l_float64  *py)
+{
+    if (!px || !py)
+        return ERROR_INT("&x and &y not both defined", __func__, 1);
+
+    *px = xr + dist * cos(radang);
+    *py = yr + dist * sin(radang);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *            Rendering function plots directly on images           *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   pixRenderPlotFromNuma()
+ *
+ * \param[in,out]  ppix        any type; replaced if not 32 bpp rgb
+ * \param[in]      na          to be plotted
+ * \param[in]      plotloc     location of plot: L_PLOT_AT_TOP, etc
+ * \param[in]      linewidth   width of "line" that is drawn; between 1 and 7
+ * \param[in]      max         maximum excursion in pixels from baseline
+ * \param[in]      color plot color: 0xrrggbb00
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Simplified interface for plotting row or column aligned data
+ *          on a pix.
+ *      (2) This replaces %pix with a 32 bpp rgb version if it is not
+ *          already 32 bpp.  It then draws the plot on the pix.
+ *      (3) See makePlotPtaFromNumaGen() for more details.
+ * </pre>
+ */
+l_ok
+pixRenderPlotFromNuma(PIX     **ppix,
+                      NUMA     *na,
+                      l_int32   plotloc,
+                      l_int32   linewidth,
+                      l_int32   max,
+                      l_uint32  color)
+{
+l_int32  w, h, size, rval, gval, bval;
+PIX     *pix1;
+PTA     *pta;
+
+    if (!ppix)
+        return ERROR_INT("&pix not defined", __func__, 1);
+    if (*ppix == NULL)
+        return ERROR_INT("pix not defined", __func__, 1);
+
+    pixGetDimensions(*ppix, &w, &h, NULL);
+    size = (plotloc == L_PLOT_AT_TOP || plotloc == L_PLOT_AT_MID_HORIZ ||
+            plotloc == L_PLOT_AT_BOT) ? h : w;
+    pta = makePlotPtaFromNuma(na, size, plotloc, linewidth, max);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+
+    if (pixGetDepth(*ppix) != 32) {
+        pix1 = pixConvertTo32(*ppix);
+        pixDestroy(ppix);
+        *ppix = pix1;
+    }
+    extractRGBValues(color, &rval, &gval, &bval);
+    pixRenderPtaArb(*ppix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   makePlotPtaFromNuma()
+ *
+ * \param[in]    na
+ * \param[in]    size        pix height for horizontal plot; pix width
+ *                           for vertical plot
+ * \param[in]    plotloc     location of plot: L_PLOT_AT_TOP, etc
+ * \param[in]    linewidth   width of "line" that is drawn; between 1 and 7
+ * \param[in]    max         maximum excursion in pixels from baseline
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This generates points from %numa representing y(x) or x(y)
+ *          with respect to a pix.  A horizontal plot y(x) is drawn for
+ *          a function of column position, and a vertical plot is drawn
+ *          for a function x(y) of row position.  The baseline is located
+ *          so that all plot points will fit in the pix.
+ *      (2) See makePlotPtaFromNumaGen() for more details.
+ * </pre>
+ */
+PTA *
+makePlotPtaFromNuma(NUMA    *na,
+                    l_int32  size,
+                    l_int32  plotloc,
+                    l_int32  linewidth,
+                    l_int32  max)
+{
+l_int32  orient, refpos;
+
+    if (!na)
+        return (PTA *)ERROR_PTR("na not defined", __func__, NULL);
+    if (plotloc == L_PLOT_AT_TOP || plotloc == L_PLOT_AT_MID_HORIZ ||
+        plotloc == L_PLOT_AT_BOT) {
+        orient = L_HORIZONTAL_LINE;
+    } else if (plotloc == L_PLOT_AT_LEFT || plotloc == L_PLOT_AT_MID_VERT ||
+               plotloc == L_PLOT_AT_RIGHT) {
+        orient = L_VERTICAL_LINE;
+    } else {
+        return (PTA *)ERROR_PTR("invalid plotloc", __func__, NULL);
+    }
+
+    if (plotloc == L_PLOT_AT_LEFT || plotloc == L_PLOT_AT_TOP)
+        refpos = max;
+    else if (plotloc == L_PLOT_AT_MID_VERT || plotloc == L_PLOT_AT_MID_HORIZ)
+        refpos = size / 2;
+    else  /* L_PLOT_AT_RIGHT || L_PLOT_AT_BOT */
+        refpos = size - max - 1;
+
+    return makePlotPtaFromNumaGen(na, orient, linewidth, refpos, max, 1);
+}
+
+
+/*!
+ * \brief   pixRenderPlotFromNumaGen()
+ *
+ * \param[in,out]  ppix         any type; replaced if not 32 bpp rgb
+ * \param[in]      na           to be plotted
+ * \param[in]      orient       L_HORIZONTAL_LINE, L_VERTICAL_LINE
+ * \param[in]      linewidth    width of "line" that is drawn; between 1 and 7
+ * \param[in]      refpos       reference position: y for horizontal;
+ *                              x for vertical
+ * \param[in]      max          maximum excursion in pixels from baseline
+ * \param[in]      drawref      1 to draw the reference line and its normal
+ * \param[in]      color        plot color: 0xrrggbb00
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) General interface for plotting row or column aligned data
+ *          on a pix.
+ *      (2) This replaces %pix with a 32 bpp rgb version if it is not
+ *          already 32 bpp.  It then draws the plot on the pix.
+ *      (3) See makePlotPtaFromNumaGen() for other input parameters.
+ * </pre>
+ */
+l_ok
+pixRenderPlotFromNumaGen(PIX     **ppix,
+                         NUMA     *na,
+                         l_int32   orient,
+                         l_int32   linewidth,
+                         l_int32   refpos,
+                         l_int32   max,
+                         l_int32   drawref,
+                         l_uint32  color)
+{
+l_int32  rval, gval, bval;
+PIX     *pix1;
+PTA     *pta;
+
+    if (!ppix)
+        return ERROR_INT("&pix not defined", __func__, 1);
+    if (*ppix == NULL)
+        return ERROR_INT("pix not defined", __func__, 1);
+
+    pta = makePlotPtaFromNumaGen(na, orient, linewidth, refpos, max, drawref);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+
+    if (pixGetDepth(*ppix) != 32) {
+        pix1 = pixConvertTo32(*ppix);
+        pixDestroy(ppix);
+        *ppix = pix1;
+    }
+    extractRGBValues(color, &rval, &gval, &bval);
+    pixRenderPtaArb(*ppix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   makePlotPtaFromNumaGen()
+ *
+ * \param[in]    na
+ * \param[in]    orient       L_HORIZONTAL_LINE, L_VERTICAL_LINE
+ * \param[in]    linewidth    width of "line" that is drawn; between 1 and 7
+ * \param[in]    refpos       reference position: y for horizontal;
+ *                            x for vertical
+ * \param[in]    max          maximum excursion in pixels from baseline
+ * \param[in]    drawref      1 to draw the reference line and its normal
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This generates points from %numa representing y(x) or x(y)
+ *          with respect to a pix.  For y(x), we draw a horizontal line
+ *          at the reference position and a vertical line at the edge; then
+ *          we draw the values of %numa, scaled so that the maximum
+ *          excursion from the reference position is %max pixels.
+ *      (2) The start and delx parameters of %numa are used to refer
+ *          its values to the raster lines (L_VERTICAL_LINE) or columns
+ *          (L_HORIZONTAL_LINE).
+ *      (3) The linewidth is chosen in the interval [1 ... 7].
+ *      (4) %refpos should be chosen so the plot is entirely within the pix
+ *          that it will be painted onto.
+ *      (5) This would typically be used to plot, in place, a function
+ *          computed along pixel rows or columns.
+ * </pre>
+ */
+PTA *
+makePlotPtaFromNumaGen(NUMA    *na,
+                       l_int32  orient,
+                       l_int32  linewidth,
+                       l_int32  refpos,
+                       l_int32  max,
+                       l_int32  drawref)
+{
+l_int32    i, n, maxw, maxh;
+l_float32  minval, maxval, absval, val, scale, start, del;
+PTA       *pta1, *pta2, *ptad;
+
+    if (!na)
+        return (PTA *)ERROR_PTR("na not defined", __func__, NULL);
+    if (orient != L_HORIZONTAL_LINE && orient != L_VERTICAL_LINE)
+        return (PTA *)ERROR_PTR("invalid orient", __func__, NULL);
+    if (linewidth < 1) {
+        L_WARNING("linewidth < 1; setting to 1\n", __func__);
+        linewidth = 1;
+    }
+    if (linewidth > 7) {
+        L_WARNING("linewidth > 7; setting to 7\n", __func__);
+        linewidth = 7;
+    }
+
+    numaGetMin(na, &minval, NULL);
+    numaGetMax(na, &maxval, NULL);
+    absval = L_MAX(L_ABS(minval), L_ABS(maxval));
+    scale = (l_float32)max / (l_float32)absval;
+    n = numaGetCount(na);
+    numaGetParameters(na, &start, &del);
+
+        /* Generate the plot points */
+    pta1 = ptaCreate(n);
+    maxw = maxh = 0;       
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (orient == L_HORIZONTAL_LINE) {
+            ptaAddPt(pta1, start + i * del, refpos + scale * val);
+            maxw = (del >= 0) ? start + n * del + linewidth
+                              : start + linewidth;
+            maxh = refpos + max + linewidth;
+        } else {  /* vertical line */
+            ptaAddPt(pta1, refpos + scale * val, start + i * del);
+            maxw = refpos + max + linewidth;
+            maxh = (del >= 0) ? start + n * del + linewidth
+                              : start + linewidth;
+        }
+    }
+
+        /* Optionally, widen the plot */
+    if (linewidth > 1) {
+        if (linewidth % 2 == 0)  /* even linewidth; use side of a square */
+            pta2 = generatePtaFilledSquare(linewidth);
+        else  /* odd linewidth; use radius of a circle */
+            pta2 = generatePtaFilledCircle(linewidth / 2);
+        ptad = ptaReplicatePattern(pta1, NULL, pta2, linewidth / 2,
+                                   linewidth / 2, maxw, maxh);
+        ptaDestroy(&pta2);
+    } else {
+        ptad = ptaClone(pta1);
+    }
+    ptaDestroy(&pta1);
+
+        /* Optionally, add the reference lines */
+    if (drawref) {
+        if (orient == L_HORIZONTAL_LINE) {
+            pta1 = generatePtaLine(start, refpos, start + n * del, refpos);
+            ptaJoin(ptad, pta1, 0, -1);
+            ptaDestroy(&pta1);
+            pta1 = generatePtaLine(start, refpos - max,
+                                   start, refpos + max);
+            ptaJoin(ptad, pta1, 0, -1);
+        } else {  /* vertical line */
+            pta1 = generatePtaLine(refpos, start, refpos, start + n * del);
+            ptaJoin(ptad, pta1, 0, -1);
+            ptaDestroy(&pta1);
+            pta1 = generatePtaLine(refpos - max, start,
+                                   refpos + max, start);
+            ptaJoin(ptad, pta1, 0, -1);
+        }
+        ptaDestroy(&pta1);
+    }
+
+    return ptad;
+}
+
+
+/*------------------------------------------------------------------*
+ *        Pta generation for arbitrary shapes built with lines      *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   pixRenderPta()
+ *
+ * \param[in]    pix   any depth, not cmapped
+ * \param[in]    pta   arbitrary set of points
+ * \param[in]    op    one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) L_SET_PIXELS puts all image bits in each pixel to 1
+ *          (black for 1 bpp; white for depth > 1)
+ *      (2) L_CLEAR_PIXELS puts all image bits in each pixel to 0
+ *          (white for 1 bpp; black for depth > 1)
+ *      (3) L_FLIP_PIXELS reverses all image bits in each pixel
+ *      (4) This function clips the rendering to the pix.  It performs
+ *          clipping for functions such as pixRenderLine(),
+ *          pixRenderBox() and pixRenderBoxa(), that call pixRenderPta().
+ * </pre>
+ */
+l_ok
+pixRenderPta(PIX     *pix,
+             PTA     *pta,
+             l_int32  op)
+{
+l_int32  i, n, x, y, w, h, d, maxval;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (pixGetColormap(pix))
+        return ERROR_INT("pix is colormapped", __func__, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", __func__, 1);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    maxval = 1;
+    if (op == L_SET_PIXELS) {
+        switch (d)
+        {
+        case 2:
+            maxval = 0x3;
+            break;
+        case 4:
+            maxval = 0xf;
+            break;
+        case 8:
+            maxval = 0xff;
+            break;
+        case 16:
+            maxval = 0xffff;
+            break;
+        case 32:
+            maxval = 0xffffffff;
+            break;
+        }
+    }
+
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w)
+            continue;
+        if (y < 0 || y >= h)
+            continue;
+        switch (op)
+        {
+        case L_SET_PIXELS:
+            pixSetPixel(pix, x, y, maxval);
+            break;
+        case L_CLEAR_PIXELS:
+            pixClearPixel(pix, x, y);
+            break;
+        case L_FLIP_PIXELS:
+            pixFlipPixel(pix, x, y);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderPtaArb()
+ *
+ * \param[in]    pix      any depth, cmapped ok
+ * \param[in]    pta      arbitrary set of points
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) If %pix is colormapped, render this color (or the nearest
+ *          color if the cmap is full) on each pixel.
+ *      (2) The rgb components have the standard dynamic range [0 ... 255]
+ *      (3) If pix is not colormapped, do the best job you can using
+ *          the input colors:
+ *          ~ d = 1: set the pixels
+ *          ~ d = 2, 4, 8: average the input rgb value
+ *          ~ d = 32: use the input rgb value
+ *      (4) This function clips the rendering to %pix.
+ * </pre>
+ */
+l_ok
+pixRenderPtaArb(PIX     *pix,
+                PTA     *pta,
+                l_uint8  rval,
+                l_uint8  gval,
+                l_uint8  bval)
+{
+l_int32   i, n, x, y, w, h, d, index;
+l_uint8   val;
+l_uint32  val32;
+PIXCMAP  *cmap;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", __func__, 1);
+    d = pixGetDepth(pix);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32)
+        return ERROR_INT("depth not in {1,2,4,8,32}", __func__, 1);
+
+    if (d == 1) {
+        pixRenderPta(pix, pta, L_SET_PIXELS);
+        return 0;
+    }
+
+    cmap = pixGetColormap(pix);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (cmap) {
+        pixcmapAddNearestColor(cmap, rval, gval, bval, &index);
+    } else {
+        if (d == 2)
+            val = (rval + gval + bval) / (3 * 64);
+        else if (d == 4)
+            val = (rval + gval + bval) / (3 * 16);
+        else if (d == 8)
+            val = (rval + gval + bval) / 3;
+        else  /* d == 32 */
+            composeRGBPixel(rval, gval, bval, &val32);
+    }
+
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w)
+            continue;
+        if (y < 0 || y >= h)
+            continue;
+        if (cmap)
+            pixSetPixel(pix, x, y, index);
+        else if (d == 32)
+            pixSetPixel(pix, x, y, val32);
+        else
+            pixSetPixel(pix, x, y, val);
+    }
+
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderPtaBlend()
+ *
+ * \param[in]    pix      32 bpp rgb
+ * \param[in]    pta      arbitrary set of points
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This function clips the rendering to %pix.
+ * </pre>
+ */
+l_ok
+pixRenderPtaBlend(PIX     *pix,
+                  PTA     *pta,
+                  l_uint8  rval,
+                  l_uint8  gval,
+                  l_uint8  bval,
+                  l_float32 fract)
+{
+l_int32    i, n, x, y, w, h;
+l_uint8    nrval, ngval, nbval;
+l_uint32   val32;
+l_float32  frval, fgval, fbval;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", __func__, 1);
+    if (pixGetDepth(pix) != 32)
+        return ERROR_INT("depth not 32 bpp", __func__, 1);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__);
+        fract = 0.5;
+    }
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    n = ptaGetCount(pta);
+    frval = fract * rval;
+    fgval = fract * gval;
+    fbval = fract * bval;
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w)
+            continue;
+        if (y < 0 || y >= h)
+            continue;
+        pixGetPixel(pix, x, y, &val32);
+        nrval = GET_DATA_BYTE(&val32, COLOR_RED);
+        nrval = (l_uint8)((1. - fract) * nrval + frval);
+        ngval = GET_DATA_BYTE(&val32, COLOR_GREEN);
+        ngval = (l_uint8)((1. - fract) * ngval + fgval);
+        nbval = GET_DATA_BYTE(&val32, COLOR_BLUE);
+        nbval = (l_uint8)((1. - fract) * nbval + fbval);
+        composeRGBPixel(nrval, ngval, nbval, &val32);
+        pixSetPixel(pix, x, y, val32);
+    }
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *           Rendering of arbitrary shapes built with lines         *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   pixRenderLine()
+ *
+ * \param[in]    pix      any depth, not cmapped
+ * \param[in]    x1, y1
+ * \param[in]    x2, y2
+ * \param[in]    width    thickness of line
+ * \param[in]    op       one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderLine(PIX     *pix,
+              l_int32  x1,
+              l_int32  y1,
+              l_int32  x2,
+              l_int32  y2,
+              l_int32  width,
+              l_int32  op)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width must be > 0; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderLineArb()
+ *
+ * \param[in]    pix       any depth, cmapped ok
+ * \param[in]    x1, y1
+ * \param[in]    x2, y2
+ * \param[in]    width     thickness of line
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderLineArb(PIX     *pix,
+                 l_int32  x1,
+                 l_int32  y1,
+                 l_int32  x2,
+                 l_int32  y2,
+                 l_int32  width,
+                 l_uint8  rval,
+                 l_uint8  gval,
+                 l_uint8  bval)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width must be > 0; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderLineBlend()
+ *
+ * \param[in]    pix      32 bpp rgb
+ * \param[in]    x1, y1
+ * \param[in]    x2, y2
+ * \param[in]    width    thickness of line
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderLineBlend(PIX       *pix,
+                   l_int32    x1,
+                   l_int32    y1,
+                   l_int32    x2,
+                   l_int32    y2,
+                   l_int32    width,
+                   l_uint8    rval,
+                   l_uint8    gval,
+                   l_uint8    bval,
+                   l_float32  fract)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width must be > 0; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderBox()
+ *
+ * \param[in]    pix     any depth, not cmapped
+ * \param[in]    box
+ * \param[in]    width   thickness of box lines
+ * \param[in]    op      one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderBox(PIX     *pix,
+             BOX     *box,
+             l_int32  width,
+             l_int32  op)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    if ((pta = generatePtaBox(box, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderBoxArb()
+ *
+ * \param[in]    pix       any depth, cmapped ok
+ * \param[in]    box
+ * \param[in]    width     thickness of box lines
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderBoxArb(PIX     *pix,
+                BOX     *box,
+                l_int32  width,
+                l_uint8  rval,
+                l_uint8  gval,
+                l_uint8  bval)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBox(box, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderBoxBlend()
+ *
+ * \param[in]    pix       32 bpp rgb
+ * \param[in]    box
+ * \param[in]    width     thickness of box lines
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract     in [0.0 - 1.0]: 1.0 is no transparency;
+ *                         0.0 is complete transparency (no effect)
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderBoxBlend(PIX       *pix,
+                  BOX       *box,
+                  l_int32    width,
+                  l_uint8    rval,
+                  l_uint8    gval,
+                  l_uint8    bval,
+                  l_float32  fract)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBox(box, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderBoxa()
+ *
+ * \param[in]    pix      any depth, not cmapped
+ * \param[in]    boxa
+ * \param[in]    width    thickness of line
+ * \param[in]    op       one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderBoxa(PIX     *pix,
+              BOXA    *boxa,
+              l_int32  width,
+              l_int32  op)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    if ((pta = generatePtaBoxa(boxa, width, 0)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderBoxaArb()
+ *
+ * \param[in]    pix       any depth; colormapped is ok
+ * \param[in]    boxa
+ * \param[in]    width     thickness of line
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderBoxaArb(PIX     *pix,
+                 BOXA    *boxa,
+                 l_int32  width,
+                 l_uint8  rval,
+                 l_uint8  gval,
+                 l_uint8  bval)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBoxa(boxa, width, 0)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderBoxaBlend()
+ *
+ * \param[in]    pix          32 bpp rgb
+ * \param[in]    boxa
+ * \param[in]    width        thickness of line
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract        in [0.0 - 1.0]: 1.0 is no transparency;
+ *                            0.0 is complete transparency (no effect)
+ * \param[in]    removedups   1 to remove; 0 otherwise
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderBoxaBlend(PIX       *pix,
+                   BOXA      *boxa,
+                   l_int32    width,
+                   l_uint8    rval,
+                   l_uint8    gval,
+                   l_uint8    bval,
+                   l_float32  fract,
+                   l_int32    removedups)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBoxa(boxa, width, removedups)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashBox()
+ *
+ * \param[in]    pix       any depth, not cmapped
+ * \param[in]    box
+ * \param[in]    spacing    spacing between lines; must be > 1
+ * \param[in]    width      thickness of box and hash lines
+ * \param[in]    orient     orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in]    outline    0 to skip drawing box outline
+ * \param[in]    op         one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderHashBox(PIX     *pix,
+                 BOX     *box,
+                 l_int32  spacing,
+                 l_int32  width,
+                 l_int32  orient,
+                 l_int32  outline,
+                 l_int32  op)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    pta = generatePtaHashBox(box, spacing, width, orient, outline);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashBoxArb()
+ *
+ * \param[in]    pix         any depth; cmapped ok
+ * \param[in]    box
+ * \param[in]    spacing     spacing between lines; must be > 1
+ * \param[in]    width       thickness of box and hash lines
+ * \param[in]    orient      orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in]    outline     0 to skip drawing box outline
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderHashBoxArb(PIX     *pix,
+                    BOX     *box,
+                    l_int32  spacing,
+                    l_int32  width,
+                    l_int32  orient,
+                    l_int32  outline,
+                    l_int32  rval,
+                    l_int32  gval,
+                    l_int32  bval)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+
+    pta = generatePtaHashBox(box, spacing, width, orient, outline);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashBoxBlend()
+ *
+ * \param[in]    pix        32 bpp
+ * \param[in]    box
+ * \param[in]    spacing    spacing between lines; must be > 1
+ * \param[in]    width      thickness of box and hash lines
+ * \param[in]    orient     orientation of lines: L_HORIZONTAL_LINE, ...
+ * \param[in]    outline    0 to skip drawing box outline
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract      in [0.0 - 1.0]: 1.0 is no transparency;
+ *                          0.0 is complete transparency (no effect)
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderHashBoxBlend(PIX       *pix,
+                      BOX       *box,
+                      l_int32    spacing,
+                      l_int32    width,
+                      l_int32    orient,
+                      l_int32    outline,
+                      l_int32    rval,
+                      l_int32    gval,
+                      l_int32    bval,
+                      l_float32  fract)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!box)
+        return ERROR_INT("box not defined", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+
+    pta = generatePtaHashBox(box, spacing, width, orient, outline);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashMaskArb()
+ *
+ * \param[in]    pix       any depth; cmapped ok
+ * \param[in]    pixm      1 bpp clipping mask for hash marks
+ * \param[in]    x,y       UL corner of %pixm with respect to %pix
+ * \param[in]    spacing   spacing between lines; must be > 1
+ * \param[in]    width     thickness of box and hash lines
+ * \param[in]    orient    orientation of lines: L_HORIZONTAL_LINE,
+ *                            L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                            L_NEG_SLOPE_LINE
+ * \param[in]    outline   0 to skip drawing box outline
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ * <pre>
+ * Notes:
+ *      (1) This is an in-place operation that renders hash lines
+ *          through a mask %pixm onto %pix.  The mask origin is
+ *          translated by (%x,%y) relative to the origin of %pix.
+ * </pre>
+ */
+l_ok
+pixRenderHashMaskArb(PIX     *pix,
+                     PIX     *pixm,
+                     l_int32  x,
+                     l_int32  y,
+                     l_int32  spacing,
+                     l_int32  width,
+                     l_int32  orient,
+                     l_int32  outline,
+                     l_int32  rval,
+                     l_int32  gval,
+                     l_int32  bval)
+{
+l_int32  w, h;
+BOX     *box1, *box2;
+PIX     *pix1;
+PTA     *pta1, *pta2;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not defined or not 1 bpp", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+
+        /* Get the points for masked hash lines */
+    pixGetDimensions(pixm, &w, &h, NULL);
+    box1 = boxCreate(0, 0, w, h);
+    pta1 = generatePtaHashBox(box1, spacing, width, orient, outline);
+    pta2 = ptaCropToMask(pta1, pixm);
+    boxDestroy(&box1);
+    ptaDestroy(&pta1);
+
+        /* Clip out the region and apply the hash lines */
+    box2 = boxCreate(x, y, w, h);
+    pix1 = pixClipRectangle(pix, box2, NULL);
+    pixRenderPtaArb(pix1, pta2, rval, gval, bval);
+    ptaDestroy(&pta2);
+    boxDestroy(&box2);
+
+        /* Rasterop the altered rectangle back in place */
+    pixRasterop(pix, x, y, w, h, PIX_SRC, pix1, 0, 0);
+    pixDestroy(&pix1);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashBoxa()
+ *
+ * \param[in]    pix       any depth, not cmapped
+ * \param[in]    boxa
+ * \param[in]    spacing   spacing between lines; must be > 1
+ * \param[in]    width     thickness of box and hash lines
+ * \param[in]    orient    orientation of lines: L_HORIZONTAL_LINE,
+ *                            L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                            L_NEG_SLOPE_LINE
+ * \param[in]    outline   0 to skip drawing box outline
+ * \param[in]    op        one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderHashBoxa(PIX     *pix,
+                  BOXA    *boxa,
+                  l_int32  spacing,
+                  l_int32  width,
+                  l_int32  orient,
+                  l_int32  outline,
+                  l_int32  op)
+ {
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashBoxaArb()
+ *
+ * \param[in]    pix         any depth; cmapped ok
+ * \param[in]    box
+ * \param[in]    spacing     spacing between lines; must be > 1
+ * \param[in]    width       thickness of box and hash lines
+ * \param[in]    orient      orientation of lines: L_HORIZONTAL_LINE,
+ *                              L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                              L_NEG_SLOPE_LINE
+ * \param[in]    outline     0 to skip drawing box outline
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderHashBoxaArb(PIX     *pix,
+                     BOXA    *boxa,
+                     l_int32  spacing,
+                     l_int32  width,
+                     l_int32  orient,
+                     l_int32  outline,
+                     l_int32  rval,
+                     l_int32  gval,
+                     l_int32  bval)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+
+    pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderHashBoxaBlend()
+ *
+ * \param[in]    pix         32 bpp rgb
+ * \param[in]    boxa
+ * \param[in]    spacing     spacing between lines; must be > 1
+ * \param[in]    width       thickness of box and hash lines
+ * \param[in]    orient      orientation of lines: L_HORIZONTAL_LINE,
+ *                              L_POS_SLOPE_LINE, L_VERTICAL_LINE,
+ *                              L_NEG_SLOPE_LINE
+ * \param[in]    outline     0 to skip drawing box outline
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract       in [0.0 - 1.0]: 1.0 is no transparency;
+ *                           0.0 is complete transparency (no effect)
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderHashBoxaBlend(PIX       *pix,
+                       BOXA      *boxa,
+                       l_int32    spacing,
+                       l_int32    width,
+                       l_int32    orient,
+                       l_int32    outline,
+                       l_int32    rval,
+                       l_int32    gval,
+                       l_int32    bval,
+                       l_float32  fract)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", __func__, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", __func__, 1);
+
+    pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
+    if (!pta)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderPolyline()
+ *
+ * \param[in]    pix         any depth, not cmapped
+ * \param[in]    ptas
+ * \param[in]    width       thickness of line
+ * \param[in]    op          one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
+ * \param[in]    closeflag   1 to close the contour; 0 otherwise
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      This renders a closed contour.
+ * </pre>
+ */
+l_ok
+pixRenderPolyline(PIX     *pix,
+                  PTA     *ptas,
+                  l_int32  width,
+                  l_int32  op,
+                  l_int32  closeflag)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!ptas)
+        return ERROR_INT("ptas not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", __func__, 1);
+
+    if ((pta = generatePtaPolyline(ptas, width, closeflag, 0)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderPolylineArb()
+ *
+ * \param[in]    pix         any depth; cmapped ok
+ * \param[in]    ptas
+ * \param[in]    width       thickness of line
+ * \param[in]    rval, gval, bval
+ * \param[in]    closeflag   1 to close the contour; 0 otherwise
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      This renders a closed contour.
+ * </pre>
+ */
+l_ok
+pixRenderPolylineArb(PIX     *pix,
+                     PTA     *ptas,
+                     l_int32  width,
+                     l_uint8  rval,
+                     l_uint8  gval,
+                     l_uint8  bval,
+                     l_int32  closeflag)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!ptas)
+        return ERROR_INT("ptas not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaPolyline(ptas, width, closeflag, 0)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderPolylineBlend()
+ *
+ * \param[in]    pix          32 bpp rgb
+ * \param[in]    ptas
+ * \param[in]    width        thickness of line
+ * \param[in]    rval, gval, bval
+ * \param[in]    fract        in [0.0 - 1.0]: 1.0 is no transparency;
+ *                            0.0 is complete transparency (no effect)
+ * \param[in]    closeflag    1 to close the contour; 0 otherwise
+ * \param[in]    removedups   1 to remove; 0 otherwise
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderPolylineBlend(PIX       *pix,
+                       PTA       *ptas,
+                       l_int32    width,
+                       l_uint8    rval,
+                       l_uint8    gval,
+                       l_uint8    bval,
+                       l_float32  fract,
+                       l_int32    closeflag,
+                       l_int32    removedups)
+{
+PTA  *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!ptas)
+        return ERROR_INT("ptas not defined", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    if ((pta = generatePtaPolyline(ptas, width, closeflag, removedups)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderGridArb()
+ *
+ * \param[in]    pix        any depth, cmapped ok
+ * \param[in]    nx, ny     number of rectangles in each direction
+ * \param[in]    width      thickness of grid lines
+ * \param[in]    rval, gval, bval
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+pixRenderGridArb(PIX     *pix,
+                 l_int32  nx,
+                 l_int32  ny,
+                 l_int32  width,
+                 l_uint8  rval,
+                 l_uint8  gval,
+                 l_uint8  bval)
+{
+l_int32  w, h;
+PTA     *pta;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (nx < 1 || ny < 1)
+        return ERROR_INT("nx, ny must be > 0", __func__, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if ((pta = generatePtaGrid(w, h, nx, ny, width)) == NULL)
+        return ERROR_INT("pta not made", __func__, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixRenderRandomCmapPtaa()
+ *
+ * \param[in]    pix          1, 2, 4, 8, 16, 32 bpp
+ * \param[in]    ptaa
+ * \param[in]    polyflag     1 to interpret each Pta as a polyline;
+ *                            0 to simply render the Pta as a set of pixels
+ * \param[in]    width        thickness of line; use only for polyline
+ * \param[in]    closeflag    1 to close the contour; 0 otherwise;
+ *                            use only for polyline mode
+ * \return  pixd cmapped, 8 bpp or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This is a debugging routine, that displays a set of
+ *          pixels, selected by the set of Ptas in a Ptaa,
+ *          in a random color in a pix.
+ *      (2) If %polyflag == 1, each Pta is considered to be a polyline,
+ *          and is rendered using %width and %closeflag.  Each polyline
+ *          is rendered in a random color.
+ *      (3) If %polyflag == 0, all points in each Pta are rendered in a
+ *          random color.  The %width and %closeflag parameters are ignored.
+ *      (4) The output pix is 8 bpp and colormapped.  Up to 254
+ *          different, randomly selected colors, can be used.
+ *      (5) The rendered pixels replace the input pixels.  They will
+ *          be clipped silently to the input pix.
+ * </pre>
+ */
+PIX  *
+pixRenderRandomCmapPtaa(PIX     *pix,
+                        PTAA    *ptaa,
+                        l_int32  polyflag,
+                        l_int32  width,
+                        l_int32  closeflag)
+{
+l_int32   i, n, index, rval, gval, bval;
+PIXCMAP  *cmap;
+PTA      *pta, *ptat;
+PIX      *pixd;
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", __func__, NULL);
+    if (!ptaa)
+        return (PIX *)ERROR_PTR("ptaa not defined", __func__, NULL);
+    if (polyflag != 0 && width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    pixd = pixConvertTo8(pix, FALSE);
+    cmap = pixcmapCreateRandom(8, 1, 1);
+    pixSetColormap(pixd, cmap);
+
+    if ((n = ptaaGetCount(ptaa)) == 0)
+        return pixd;
+
+    for (i = 0; i < n; i++) {
+        index = 1 + (i % 254);
+        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        if (polyflag)
+            ptat = generatePtaPolyline(pta, width, closeflag, 0);
+        else
+            ptat = ptaClone(pta);
+        pixRenderPtaArb(pixd, ptat, rval, gval, bval);
+        ptaDestroy(&pta);
+        ptaDestroy(&ptat);
+    }
+
+    return pixd;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *                Rendering and filling of polygons                 *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   pixRenderPolygon()
+ *
+ * \param[in]    ptas     of vertices, none repeated
+ * \param[in]    width    of polygon outline
+ * \param[out]   pxmin    [optional] min x value of input pts
+ * \param[out]   pymin    [optional] min y value of input pts
+ * \return  pix 1 bpp, with outline generated, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The pix is the minimum size required to contain the origin
+ *          and the polygon.  For example, the max x value of the input
+ *          points is w - 1, where w is the pix width.
+ *      (2) The rendered line is 4-connected, so that an interior or
+ *          exterior 8-c.c. flood fill operation works properly.
+ * </pre>
+ */
+PIX *
+pixRenderPolygon(PTA      *ptas,
+                 l_int32   width,
+                 l_int32  *pxmin,
+                 l_int32  *pymin)
+{
+l_float32  fxmin, fxmax, fymin, fymax;
+PIX       *pixd;
+PTA       *pta1, *pta2;
+
+    if (pxmin) *pxmin = 0;
+    if (pymin) *pymin = 0;
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL);
+
+        /* Generate a 4-connected polygon line */
+    if ((pta1 = generatePtaPolyline(ptas, width, 1, 0)) == NULL)
+        return (PIX *)ERROR_PTR("pta1 not made", __func__, NULL);
+    if (width < 2)
+        pta2 = convertPtaLineTo4cc(pta1);
+    else
+        pta2 = ptaClone(pta1);
+
+        /* Render onto a minimum-sized pix */
+    ptaGetRange(pta2, &fxmin, &fxmax, &fymin, &fymax);
+    if (pxmin) *pxmin = (l_int32)(fxmin + 0.5);
+    if (pymin) *pymin = (l_int32)(fymin + 0.5);
+    pixd = pixCreate((l_int32)(fxmax + 0.5) + 1, (l_int32)(fymax + 0.5) + 1, 1);
+    pixRenderPolyline(pixd, pta2, width, L_SET_PIXELS, 1);
+    ptaDestroy(&pta1);
+    ptaDestroy(&pta2);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixFillPolygon()
+ *
+ * \param[in]    pixs          1 bpp, with 4-connected polygon outline
+ * \param[in]    pta           vertices of the polygon
+ * \param[in]    xmin, ymin    min values of vertices of polygon
+ * \return  pixd with outline filled, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This fills the interior of the polygon, returning a
+ *          new pix.  It works for both convex and non-convex polygons.
+ *      (2) To generate a filled polygon from %pta:
+ *            PIX *pixt = pixRenderPolygon(pta, 1, &xmin, &ymin);
+ *            PIX *pixd = pixFillPolygon(pixt, pta, xmin, ymin);
+ *            pixDestroy(&pixt);
+ * </pre>
+ */
+PIX *
+pixFillPolygon(PIX     *pixs,
+               PTA     *pta,
+               l_int32  xmin,
+               l_int32  ymin)
+{
+l_int32   w, h, i, n, inside, found;
+l_int32  *xstart, *xend;
+PIX      *pixi, *pixd;
+
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
+    if (!pta)
+        return (PIX *)ERROR_PTR("pta not defined", __func__, NULL);
+    if (ptaGetCount(pta) < 2)
+        return (PIX *)ERROR_PTR("pta has < 2 pts", __func__, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    xstart = (l_int32 *)LEPT_CALLOC(L_MAX(1, w / 2), sizeof(l_int32));
+    xend = (l_int32 *)LEPT_CALLOC(L_MAX(1, w / 2), sizeof(l_int32));
+    if (!xstart || !xend) {
+        LEPT_FREE(xstart);
+        LEPT_FREE(xend);
+        return (PIX *)ERROR_PTR("xstart and xend not made", __func__, NULL);
+    }
+
+        /* Find a raster with 2 or more black runs.  The first background
+         * pixel after the end of the first run is likely to be inside
+         * the polygon, and can be used as a seed pixel. */
+    found = FALSE;
+    for (i = ymin + 1; i < h; i++) {
+        pixFindHorizontalRuns(pixs, i, xstart, xend, &n);
+        if (n > 1) {
+            ptaPtInsidePolygon(pta, xend[0] + 1, i, &inside);
+            if (inside) {
+                found = TRUE;
+                break;
+            }
+        }
+    }
+    if (!found) {
+        L_WARNING("nothing found to fill\n", __func__);
+        LEPT_FREE(xstart);
+        LEPT_FREE(xend);
+        return 0;
+    }
+
+        /* Place the seed pixel in the output image */
+    pixd = pixCreateTemplate(pixs);
+    pixSetPixel(pixd, xend[0] + 1, i, 1);
+
+        /* Invert pixs to make a filling mask, and fill from the seed */
+    pixi = pixInvert(NULL, pixs);
+    pixSeedfillBinary(pixd, pixd, pixi, 4);
+
+        /* Add the pixels of the original polygon outline */
+    pixOr(pixd, pixd, pixs);
+
+    pixDestroy(&pixi);
+    LEPT_FREE(xstart);
+    LEPT_FREE(xend);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Contour rendering on grayscale images                *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   pixRenderContours()
+ *
+ * \param[in]    pixs        8 or 16 bpp; no colormap
+ * \param[in]    startval    value of lowest contour; must be in [0 ... maxval]
+ * \param[in]    incr        increment to next contour; must be > 0
+ * \param[in]    outdepth    either 1 or depth of pixs
+ * \return  pixd, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The output can be either 1 bpp, showing just the contour
+ *          lines, or a copy of the input pixs with the contour lines
+ *          superposed.
+ * </pre>
+ */
+PIX *
+pixRenderContours(PIX     *pixs,
+                  l_int32  startval,
+                  l_int32  incr,
+                  l_int32  outdepth)
+{
+l_int32    w, h, d, maxval, wpls, wpld, i, j, val, test;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16)
+        return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", __func__, NULL);
+    if (outdepth != 1 && outdepth != d) {
+        L_WARNING("invalid outdepth; setting to 1\n", __func__);
+        outdepth = 1;
+    }
+    maxval = (1 << d) - 1;
+    if (startval < 0 || startval > maxval)
+        return (PIX *)ERROR_PTR("startval not in [0 ... maxval]",
+               __func__, NULL);
+    if (incr < 1)
+        return (PIX *)ERROR_PTR("incr < 1", __func__, NULL);
+
+    if (outdepth == d)
+        pixd = pixCopy(NULL, pixs);
+    else
+        pixd = pixCreate(w, h, 1);
+
+    pixCopyResolution(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    switch (d)
+    {
+    case 8:
+        if (outdepth == 1) {
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_BYTE(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        } else {  /* outdepth == d */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_BYTE(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_BYTE(lined, j, 0);
+                }
+            }
+        }
+        break;
+
+    case 16:
+        if (outdepth == 1) {
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_TWO_BYTES(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        } else {  /* outdepth == d */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_TWO_BYTES(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_TWO_BYTES(lined, j, 0);
+                }
+            }
+        }
+        break;
+
+    default:
+        return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", __func__, NULL);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ * \brief   fpixAutoRenderContours()
+ *
+ * \param[in]    fpix
+ * \param[in]    ncontours   in [2 ... 500]; typically about 50
+ * \return  pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The increment is set to get approximately %ncontours.
+ *      (2) The proximity to the target value for contour display
+ *          is set to 0.15.
+ *      (3) Negative values are rendered in red; positive values as black.
+ * </pre>
+ */
+PIX *
+fpixAutoRenderContours(FPIX    *fpix,
+                       l_int32  ncontours)
+{
+l_float32  minval, maxval, incr;
+
+    if (!fpix)
+        return (PIX *)ERROR_PTR("fpix not defined", __func__, NULL);
+    if (ncontours < 2 || ncontours > 500)
+        return (PIX *)ERROR_PTR("ncontours < 2 or > 500", __func__, NULL);
+
+    fpixGetMin(fpix, &minval, NULL, NULL);
+    fpixGetMax(fpix, &maxval, NULL, NULL);
+    if (minval == maxval)
+        return (PIX *)ERROR_PTR("all values in fpix are equal", __func__, NULL);
+    incr = (maxval - minval) / ((l_float32)ncontours - 1);
+    return fpixRenderContours(fpix, incr, 0.15f);
+}
+
+
+/*!
+ * \brief   fpixRenderContours()
+ *
+ * \param[in]    fpixs
+ * \param[in]    incr      increment between contours; must be > 0.0
+ * \param[in]    proxim    required proximity to target value; default 0.15
+ * \return  pixd 8 bpp, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Values are displayed when val/incr is within +-proxim
+ *          to an integer.  The default value is 0.15; smaller values
+ *          result in thinner contour lines.
+ *      (2) Negative values are rendered in red; positive values as black.
+ * </pre>
+ */
+PIX *
+fpixRenderContours(FPIX      *fpixs,
+                   l_float32  incr,
+                   l_float32  proxim)
+{
+l_int32     i, j, w, h, wpls, wpld;
+l_float32   val, invincr, finter, above, below, diff;
+l_uint32   *datad, *lined;
+l_float32  *datas, *lines;
+PIX        *pixd;
+PIXCMAP    *cmap;
+
+    if (!fpixs)
+        return (PIX *)ERROR_PTR("fpixs not defined", __func__, NULL);
+    if (incr <= 0.0)
+        return (PIX *)ERROR_PTR("incr <= 0.0", __func__, NULL);
+    if (proxim <= 0.0)
+        proxim = 0.15f;  /* default */
+
+    fpixGetDimensions(fpixs, &w, &h);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
+    cmap = pixcmapCreate(8);
+    pixSetColormap(pixd, cmap);
+    pixcmapAddColor(cmap, 255, 255, 255);  /* white */
+    pixcmapAddColor(cmap, 0, 0, 0);  /* black */
+    pixcmapAddColor(cmap, 255, 0, 0);  /* red */
+
+    datas = fpixGetData(fpixs);
+    wpls = fpixGetWpl(fpixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    invincr = 1.0 / incr;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j];
+            finter = invincr * val;
+            above = finter - floorf(finter);
+            below = ceilf(finter) - finter;
+            diff = L_MIN(above, below);
+            if (diff <= proxim) {
+                if (val < 0.0)
+                    SET_DATA_BYTE(lined, j, 2);
+                else
+                    SET_DATA_BYTE(lined, j, 1);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Boundary pt generation on 1 bpp images               *
+ *------------------------------------------------------------------*/
+/*!
+ * \brief   pixGeneratePtaBoundary()
+ *
+ * \param[in]    pixs     1 bpp
+ * \param[in]    width    of boundary line
+ * \return  pta, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Similar to ptaGetBoundaryPixels(), except here:
+ *          * we only get pixels in the foreground
+ *          * we can have a "line" width greater than 1 pixel.
+ *      (2) Once generated, this can be applied to a random 1 bpp image
+ *          to add a color boundary as follows:
+ *             Pta *pta = pixGeneratePtaBoundary(pixs, width);
+ *             Pix *pix1 = pixConvert1To8Cmap(pixs);
+ *             pixRenderPtaArb(pix1, pta, rval, gval, bval);
+ * </pre>
+ */
+PTA  *
+pixGeneratePtaBoundary(PIX     *pixs,
+                       l_int32  width)
+{
+PIX  *pix1;
+PTA  *pta;
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", __func__);
+        width = 1;
+    }
+
+    pix1 = pixErodeBrick(NULL, pixs, 2 * width + 1, 2 * width + 1);
+    pixXor(pix1, pix1, pixs);
+    pta = ptaGetPixelsFromPix(pix1, NULL);
+    pixDestroy(&pix1);
+    return pta;
+}