diff mupdf-source/thirdparty/leptonica/src/ptafunc2.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/ptafunc2.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,806 @@
+/*====================================================================*
+ -  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  ptafunc2.c
+ * <pre>
+ *
+ *      --------------------------------------
+ *      This file has these Pta utilities:
+ *         - sorting
+ *         - ordered set operations
+ *         - hash map operations
+ *      --------------------------------------
+ *
+ *      Sorting
+ *           PTA        *ptaSort()
+ *           l_int32     ptaGetSortIndex()
+ *           PTA        *ptaSortByIndex()
+ *           PTAA       *ptaaSortByIndex()
+ *           l_int32     ptaGetRankValue()
+ *           PTA        *ptaSort2d()
+ *           l_int32     ptaEqual()
+ *
+ *      Set operations using aset (rbtree)
+ *           L_ASET     *l_asetCreateFromPta()
+ *           PTA        *ptaRemoveDupsByAset()
+ *           PTA        *ptaUnionByAset()
+ *           PTA        *ptaIntersectionByAset()
+ *
+ *      Hashmap operations
+ *          L_HASHMAP   *l_hmapCreateFromPta()
+ *          l_int32      ptaRemoveDupsByHmap()
+ *          l_int32      ptaUnionByHmap()
+ *          l_int32      ptaIntersectionByHmap()
+ *
+ * We have two implementations of set operations on an array of points:
+ *
+ *   (1) Using an underlying tree (rbtree)
+ *       This uses a good 64 bit hashing function for the key,
+ *       that is not expected to have hash collisions (and we do
+ *       not test for them).  The tree is built up of the keys,
+ *       values, and is traversed looking for the key in O(log n).
+ *
+ *   (2) Building a hashmap from the keys (hashmap)
+ *       This uses a fast 64 bit hashing function for the key, which
+ *       is then hashed into a hashtable.  Collisions of hashkeys are
+ *       very rare, but the hashtable is designed to allow more than one
+ *       hashitem in a table entry.  The hashitems are put in a list at
+ *       each hashtable entry, which is traversed looking for the key.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ *                               Sorting                               *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   ptaSort()
+ *
+ * \param[in]    ptas
+ * \param[in]    sorttype    L_SORT_BY_X, L_SORT_BY_Y
+ * \param[in]    sortorder   L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out]   pnaindex    [optional] index of sorted order into
+ *                           original array
+ * \return  ptad sorted version of ptas, or NULL on error
+ */
+PTA *
+ptaSort(PTA     *ptas,
+        l_int32  sorttype,
+        l_int32  sortorder,
+        NUMA   **pnaindex)
+{
+PTA   *ptad;
+NUMA  *naindex;
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y)
+        return (PTA *)ERROR_PTR("invalid sort type", __func__, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (PTA *)ERROR_PTR("invalid sort order", __func__, NULL);
+
+    if (ptaGetSortIndex(ptas, sorttype, sortorder, &naindex) != 0)
+        return (PTA *)ERROR_PTR("naindex not made", __func__, NULL);
+
+    ptad = ptaSortByIndex(ptas, naindex);
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    if (!ptad)
+        return (PTA *)ERROR_PTR("ptad not made", __func__, NULL);
+    return ptad;
+}
+
+
+/*!
+ * \brief   ptaGetSortIndex()
+ *
+ * \param[in]    ptas
+ * \param[in]    sorttype    L_SORT_BY_X, L_SORT_BY_Y
+ * \param[in]    sortorder   L_SORT_INCREASING, L_SORT_DECREASING
+ * \param[out]   pnaindex    index of sorted order into original array
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+ptaGetSortIndex(PTA     *ptas,
+                l_int32  sorttype,
+                l_int32  sortorder,
+                NUMA   **pnaindex)
+{
+l_int32    i, n;
+l_float32  x, y;
+NUMA      *na, *nai;
+
+    if (!pnaindex)
+        return ERROR_INT("&naindex not defined", __func__, 1);
+    *pnaindex = NULL;
+    if (!ptas)
+        return ERROR_INT("ptas not defined", __func__, 1);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y)
+        return ERROR_INT("invalid sort type", __func__, 1);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return ERROR_INT("invalid sort order", __func__, 1);
+
+        /* Build up numa of specific data */
+    n = ptaGetCount(ptas);
+    if ((na = numaCreate(n)) == NULL)
+        return ERROR_INT("na not made", __func__, 1);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        if (sorttype == L_SORT_BY_X)
+            numaAddNumber(na, x);
+        else
+            numaAddNumber(na, y);
+    }
+
+        /* Get the sort index for data array */
+    nai = numaGetSortIndex(na, sortorder);
+    numaDestroy(&na);
+    if (!nai)
+        return ERROR_INT("naindex not made", __func__, 1);
+    *pnaindex = nai;
+    return 0;
+}
+
+
+/*!
+ * \brief   ptaSortByIndex()
+ *
+ * \param[in]    ptas
+ * \param[in]    naindex    na that maps from the new pta to the input pta
+ * \return  ptad sorted, or NULL on  error
+ */
+PTA *
+ptaSortByIndex(PTA   *ptas,
+               NUMA  *naindex)
+{
+l_int32    i, index, n;
+l_float32  x, y;
+PTA       *ptad;
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL);
+    if (!naindex)
+        return (PTA *)ERROR_PTR("naindex not defined", __func__, NULL);
+
+        /* Build up sorted pta using sort index */
+    n = numaGetCount(naindex);
+    if ((ptad = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", __func__, NULL);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        ptaGetPt(ptas, index, &x, &y);
+        ptaAddPt(ptad, x, y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ * \brief   ptaaSortByIndex()
+ *
+ * \param[in]    ptaas
+ * \param[in]    naindex    na that maps from the new ptaa to the input ptaa
+ * \return  ptaad sorted, or NULL on error
+ */
+PTAA *
+ptaaSortByIndex(PTAA  *ptaas,
+                NUMA  *naindex)
+{
+l_int32  i, n, index;
+PTA     *pta;
+PTAA    *ptaad;
+
+    if (!ptaas)
+        return (PTAA *)ERROR_PTR("ptaas not defined", __func__, NULL);
+    if (!naindex)
+        return (PTAA *)ERROR_PTR("naindex not defined", __func__, NULL);
+
+    n = ptaaGetCount(ptaas);
+    if (numaGetCount(naindex) != n)
+        return (PTAA *)ERROR_PTR("numa and ptaa sizes differ", __func__, NULL);
+    ptaad = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        pta = ptaaGetPta(ptaas, index, L_COPY);
+        ptaaAddPta(ptaad, pta, L_INSERT);
+    }
+
+    return ptaad;
+}
+
+
+/*!
+ * \brief   ptaGetRankValue()
+ *
+ * \param[in]    pta
+ * \param[in]    fract      use 0.0 for smallest, 1.0 for largest
+ * \param[in]    ptasort    [optional] version of %pta sorted by %sorttype
+ * \param[in]    sorttype   L_SORT_BY_X, L_SORT_BY_Y
+ * \param[out]   pval       rankval: the x or y value at %fract
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+ptaGetRankValue(PTA        *pta,
+                l_float32   fract,
+                PTA        *ptasort,
+                l_int32     sorttype,
+                l_float32  *pval)
+{
+l_int32  index, n;
+PTA     *ptas;
+
+    if (!pval)
+        return ERROR_INT("&val not defined", __func__, 1);
+    *pval = 0.0;
+    if (!pta)
+        return ERROR_INT("pta not defined", __func__, 1);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y)
+        return ERROR_INT("invalid sort type", __func__, 1);
+    if (fract < 0.0 || fract > 1.0)
+        return ERROR_INT("fract not in [0.0 ... 1.0]", __func__, 1);
+    if ((n = ptaGetCount(pta)) == 0)
+        return ERROR_INT("pta empty", __func__, 1);
+
+    if (ptasort)
+        ptas = ptasort;
+    else
+        ptas = ptaSort(pta, sorttype, L_SORT_INCREASING, NULL);
+
+    index = (l_int32)(fract * (l_float32)(n - 1) + 0.5);
+    if (sorttype == L_SORT_BY_X)
+        ptaGetPt(ptas, index, pval, NULL);
+    else  /* sort by y */
+        ptaGetPt(ptas, index, NULL, pval);
+
+    if (!ptasort) ptaDestroy(&ptas);
+    return 0;
+}
+
+
+/*!
+ * \brief   ptaSort2d()
+ *
+ * \param[in]    ptas
+ * \return  ptad, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Sort increasing by row-major, scanning down from the UL corner,
+ *          where for each value of y, order the pts from left to right.
+ * </pre>
+ */
+PTA *
+ptaSort2d(PTA  *pta)
+{
+l_int32    index, i, j, n, nx, ny, start, end;
+l_float32  x, y, yp, val;
+NUMA      *na1, *na2, *nas, *nax;
+PTA       *pta1, *ptad;
+
+    if (!pta)
+        return (PTA *)ERROR_PTR("pta not defined", __func__, NULL);
+
+        /* Sort by row-major (y first, then x).  After sort by y,
+         * the x values at the same y are not sorted.  */
+    pta1 = ptaSort(pta, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+
+        /* Find start and ending indices with the same y value */
+    n = ptaGetCount(pta1);
+    na1 = numaCreate(0);  /* holds start index of sequence with same y */
+    na2 = numaCreate(0);  /* holds end index of sequence with same y */
+    numaAddNumber(na1, 0);
+    ptaGetPt(pta1, 0, &x, &yp);
+    for (i = 1; i < n; i++) {
+        ptaGetPt(pta1, i, &x, &y);
+        if (y != yp) {
+            numaAddNumber(na1, i);
+            numaAddNumber(na2, i - 1);
+        }
+        yp = y;
+    }
+    numaAddNumber(na2, n - 1);
+
+        /* Sort by increasing x each set with the same y value */
+    ptad = ptaCreate(n);
+    ny = numaGetCount(na1);   /* number of distinct y values */
+    for (i = 0, index = 0; i < ny; i++) {
+        numaGetIValue(na1, i, &start);
+        numaGetIValue(na2, i, &end);
+        nx = end - start + 1;  /* number of points with current y value */
+        if (nx == 1) {
+            ptaGetPt(pta1, index++, &x, &y);
+            ptaAddPt(ptad, x, y);
+        } else {
+                /* More than 1 point; extract and sort the x values */
+            nax = numaCreate(nx);
+            for (j = 0; j < nx; j++) {
+                 ptaGetPt(pta1, index + j, &x, &y);
+                 numaAddNumber(nax, x);
+            }
+            nas = numaSort(NULL, nax, L_SORT_INCREASING);
+                /* Add the points with x sorted */
+            for (j = 0; j < nx; j++) {
+                numaGetFValue(nas, j, &val);
+                ptaAddPt(ptad, val, y);
+            }
+            index += nx;
+            numaDestroy(&nax);
+            numaDestroy(&nas);
+        }
+    }
+    numaDestroy(&na1);
+    numaDestroy(&na2);
+    ptaDestroy(&pta1);
+    return ptad;
+}
+
+
+/*!
+ * \brief   ptaEqual()
+ *
+ * \param[in]    pta1
+ * \param[in]    pta2
+ * \param[out]   psame  1 if same; 0 if different
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Equality is defined as having the same set of points,
+ *          independent of the order in which they are presented.
+ * </pre>
+ */
+l_ok
+ptaEqual(PTA      *pta1,
+         PTA      *pta2,
+         l_int32  *psame)
+{
+l_int32    i, n1, n2;
+l_float32  x1, y1, x2, y2;
+PTA       *ptas1, *ptas2;
+
+    if (!psame)
+        return ERROR_INT("&same not defined", __func__, 1);
+    *psame = 0.0f;
+    if (!pta1 || !pta2)
+        return ERROR_INT("pta1 and pta2 not both defined", __func__, 1);
+
+    n1 = ptaGetCount(pta1);
+    n2 = ptaGetCount(pta2);
+    if (n1 != n2) return 0;
+
+        /* 2d sort each and compare */
+    ptas1 = ptaSort2d(pta1);
+    ptas2 = ptaSort2d(pta2);
+    for (i = 0; i < n1; i++) {
+        ptaGetPt(ptas1, i, &x1, &y1);
+        ptaGetPt(ptas2, i, &x2, &y2);
+        if (x1 != x2 || y1 != y2) {
+            ptaDestroy(&ptas1);
+            ptaDestroy(&ptas2);
+            return 0;
+        }
+    }
+
+    *psame = 1;
+    ptaDestroy(&ptas1);
+    ptaDestroy(&ptas2);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                   Set operations using aset (rbtree)                *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   l_asetCreateFromPta()
+ *
+ * \param[in]    pta
+ * \return  set using a 64-bit hash of (x,y) as the key
+ */
+L_ASET *
+l_asetCreateFromPta(PTA  *pta)
+{
+l_int32   i, n, x, y;
+l_uint64  hash;
+L_ASET   *set;
+RB_TYPE   key;
+
+    if (!pta)
+        return (L_ASET *)ERROR_PTR("pta not defined", __func__, NULL);
+
+    set = l_asetCreate(L_UINT_TYPE);
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        l_hashPtToUint64(x, y, &hash);
+        key.utype = hash;
+        l_asetInsert(set, key);
+    }
+
+    return set;
+}
+
+
+/*!
+ * \brief   ptaRemoveDupsByAset()
+ *
+ * \param[in]    ptas     assumed to be integer values
+ * \param[out]   pptad    assumed to be integer values
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This is slower than ptaRemoveDupsByHmap(), mostly because
+ *          of the nlogn sort to build up the rbtree.  Do not use for
+ *          large numbers of points (say, > 100K).
+ * </pre>
+ */
+l_ok
+ptaRemoveDupsByAset(PTA   *ptas,
+                    PTA  **pptad)
+{
+l_int32   i, n, x, y;
+PTA      *ptad;
+l_uint64  hash;
+L_ASET   *set;
+RB_TYPE   key;
+
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", __func__, 1);
+    *pptad = NULL;
+    if (!ptas)
+        return ERROR_INT("ptas not defined", __func__, 1);
+
+    set = l_asetCreate(L_UINT_TYPE);
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(n);
+    *pptad = ptad;
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        l_hashPtToUint64(x, y, &hash);
+        key.utype = hash;
+        if (!l_asetFind(set, key)) {
+            ptaAddPt(ptad, x, y);
+            l_asetInsert(set, key);
+        }
+    }
+
+    l_asetDestroy(&set);
+    return 0;
+}
+
+
+/*!
+ * \brief   ptaUnionByAset()
+ *
+ * \param[in]    pta1
+ * \param[in]    pta2
+ * \param[out]   pptad     union of the two point arrays
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) See sarrayRemoveDupsByAset() for the approach.
+ *      (2) The key is a 64-bit hash from the (x,y) pair.
+ *      (3) This is slower than ptaUnionByHmap(), mostly because of the
+ *          nlogn sort to build up the rbtree.  Do not use for large
+ *          numbers of points (say, > 100K).
+ *      (4) The *Aset() functions use the sorted l_Aset, which is just
+ *          an rbtree in disguise.
+ * </pre>
+ */
+l_ok
+ptaUnionByAset(PTA   *pta1,
+               PTA   *pta2,
+               PTA  **pptad)
+{
+PTA  *pta3;
+
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", __func__, 1);
+    *pptad = NULL;
+    if (!pta1)
+        return ERROR_INT("pta1 not defined", __func__, 1);
+    if (!pta2)
+        return ERROR_INT("pta2 not defined", __func__, 1);
+
+        /* Join */
+    pta3 = ptaCopy(pta1);
+    ptaJoin(pta3, pta2, 0, -1);
+
+        /* Eliminate duplicates */
+    ptaRemoveDupsByAset(pta3, pptad);
+    ptaDestroy(&pta3);
+    return 0;
+}
+
+
+/*!
+ * \brief   ptaIntersectionByAset()
+ *
+ * \param[in]    pta1
+ * \param[in]    pta2
+ * \param[out]   pptad       intersection of the two point arrays
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) See sarrayIntersectionByAset() for the approach.
+ *      (2) The key is a 64-bit hash from the (x,y) pair.
+ *      (3) This is slower than ptaIntersectionByHmap(), mostly because
+ *          of the nlogn sort to build up the rbtree.  Do not use for
+ *          large numbers of points (say, > 100K).
+ * </pre>
+ */
+l_ok
+ptaIntersectionByAset(PTA   *pta1,
+                      PTA   *pta2,
+                      PTA  **pptad)
+{
+l_int32   n1, n2, i, n, x, y;
+l_uint64  hash;
+L_ASET   *set1, *set2;
+RB_TYPE   key;
+PTA      *pta_small, *pta_big, *ptad;
+
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", __func__, 1);
+    *pptad = NULL;
+    if (!pta1)
+        return ERROR_INT("pta1 not defined", __func__, 1);
+    if (!pta2)
+        return ERROR_INT("pta2 not defined", __func__, 1);
+
+        /* Put the elements of the biggest array into a set */
+    n1 = ptaGetCount(pta1);
+    n2 = ptaGetCount(pta2);
+    pta_small = (n1 < n2) ? pta1 : pta2;   /* do not destroy pta_small */
+    pta_big = (n1 < n2) ? pta2 : pta1;   /* do not destroy pta_big */
+    set1 = l_asetCreateFromPta(pta_big);
+
+        /* Build up the intersection of points */
+    ptad = ptaCreate(0);
+    *pptad = ptad;
+    n = ptaGetCount(pta_small);
+    set2 = l_asetCreate(L_UINT_TYPE);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta_small, i, &x, &y);
+        l_hashPtToUint64(x, y, &hash);
+        key.utype = hash;
+        if (l_asetFind(set1, key) && !l_asetFind(set2, key)) {
+            ptaAddPt(ptad, x, y);
+            l_asetInsert(set2, key);
+        }
+    }
+
+    l_asetDestroy(&set1);
+    l_asetDestroy(&set2);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                            Hashmap operations                            *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief  l_hmapCreateFromPta()
+ *
+ * \param[in]   pta     input pta
+ * \return      hmap    hashmap, or NULL on error
+ *
+ * <pre>
+ *  Notes:
+ *       (1) The indices into %pta are stored in the val field of the hashitems.
+ *           This is necessary so that %hmap and %pta can be used together.
+ * </pre>
+ */
+L_HASHMAP *
+l_hmapCreateFromPta(PTA  *pta)
+{
+l_int32      i, n, x, y;
+l_uint64     key;
+L_HASHMAP   *hmap;
+
+    if (!pta)
+        return (L_HASHMAP *)ERROR_PTR("pta not defined", __func__, NULL);
+
+    n = ptaGetCount(pta);
+    if ((hmap = l_hmapCreate(0.51 * n, 2)) == NULL)
+        return (L_HASHMAP *)ERROR_PTR("hmap not made", __func__, NULL);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        l_hashPtToUint64(x, y, &key);
+        l_hmapLookup(hmap, key, i, L_HMAP_CREATE);
+    }
+    return hmap;
+}
+
+
+/*!
+ * \brief  ptaRemoveDupsByHmap()
+ *
+ * \param[in]   ptas
+ * \param[out]  pptad    set of unique values
+ * \param[out]  phmap    [optional] hashmap used for lookup
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ *  Notes:
+ *       (1) Generates a set of (unique) points from %ptas.
+ * </pre>
+ */
+l_ok
+ptaRemoveDupsByHmap(PTA         *ptas,
+                    PTA        **pptad,
+                    L_HASHMAP  **phmap)
+{
+l_int32      i, x, y, tabsize;
+PTA         *ptad;
+L_HASHITEM  *hitem;
+L_HASHMAP   *hmap;
+
+    if (phmap) *phmap = NULL;
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", __func__, 1);
+    *pptad = NULL;
+    if (!ptas)
+        return ERROR_INT("ptas not defined", __func__, 1);
+
+        /* Traverse the hashtable lists */
+    if ((hmap = l_hmapCreateFromPta(ptas)) == NULL)
+        return ERROR_INT("hmap not made", __func__, 1);
+    ptad = ptaCreate(0);
+    *pptad = ptad;
+    tabsize = hmap->tabsize;
+    for (i = 0; i < tabsize; i++) {
+        hitem = hmap->hashtab[i];
+        while (hitem) {
+            ptaGetIPt(ptas, hitem->val, &x, &y);
+            ptaAddPt(ptad, x, y);
+            hitem = hitem->next;
+        }
+    }
+
+    if (phmap)
+        *phmap = hmap;
+    else
+        l_hmapDestroy(&hmap);
+    return 0;
+}
+
+
+/*!
+ * \brief  ptaUnionByHmap()
+ *
+ * \param[in]   pta1
+ * \param[in]   pta2
+ * \param[out]  pptad     union of the two point arrays
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ *  Notes:
+ *       (1) Make pta with points found in either of the input arrays.
+ * </pre>
+ */
+l_ok
+ptaUnionByHmap(PTA   *pta1,
+               PTA   *pta2,
+               PTA  **pptad)
+{
+PTA  *pta3;
+
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", __func__, 1);
+    *pptad = NULL;
+    if (!pta1)
+        return ERROR_INT("pta1 not defined", __func__, 1);
+    if (!pta2)
+        return ERROR_INT("pta2 not defined", __func__, 1);
+
+    pta3 = ptaCopy(pta1);
+    if (ptaJoin(pta3, pta2, 0, -1) == 1) {
+        ptaDestroy(&pta3);
+        return ERROR_INT("pta join failed", __func__, 1);
+    }
+    ptaRemoveDupsByHmap(pta3, pptad, NULL);
+    ptaDestroy(&pta3);
+    return 0;
+}
+
+
+/*!
+ * \brief  ptaIntersectionByHmap()
+ *
+ * \param[in]    pta1
+ * \param[in]    pta2
+ * \param[out]   pptad     intersection of the two point arrays
+ * \return  0 if OK; 1 on error
+ *
+ * <pre>
+ *  Notes:
+ *       (1) Make pta with pts common to both input arrays.
+ * </pre>
+ */
+l_ok
+ptaIntersectionByHmap(PTA   *pta1,
+                      PTA   *pta2,
+                      PTA  **pptad)
+{
+l_int32      i, n1, n2, n, x, y;
+l_uint64     key;
+PTA         *pta_small, *pta_big, *ptad;
+L_HASHITEM  *hitem;
+L_HASHMAP   *hmap;
+
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", __func__, 1);
+    *pptad = NULL;
+    if (!pta1)
+        return ERROR_INT("pta1 not defined", __func__, 1);
+    if (!pta2)
+        return ERROR_INT("pta2 not defined", __func__, 1);
+
+        /* Make a hashmap for the elements of the biggest array */
+    n1 = ptaGetCount(pta1);
+    n2 = ptaGetCount(pta2);
+    pta_small = (n1 < n2) ? pta1 : pta2;   /* do not destroy pta_small */
+    pta_big = (n1 < n2) ? pta2 : pta1;   /* do not destroy pta_big */
+    if ((hmap = l_hmapCreateFromPta(pta_big)) == NULL)
+        return ERROR_INT("hmap not made", __func__, 1);
+
+        /* Go through the smallest array, doing a lookup of its (x,y) into
+         * the big array hashmap.  If an hitem is returned, check the count.
+         * If the count is 0, ignore; otherwise, add the point to the
+         * output ptad and set the count in the hitem to 0, indicating
+         * that the point has already been added. */
+    ptad = ptaCreate(0);
+    *pptad = ptad;
+    n = ptaGetCount(pta_small);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta_small, i, &x, &y);
+        l_hashPtToUint64(x, y, &key);
+        hitem = l_hmapLookup(hmap, key, i, L_HMAP_CHECK);
+        if (!hitem || hitem->count == 0)
+            continue;
+        ptaAddPt(ptad, x, y);
+        hitem->count = 0;
+    }
+    l_hmapDestroy(&hmap);
+    return 0;
+}
+
+