diff mupdf-source/thirdparty/leptonica/src/checkerboard.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/checkerboard.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,312 @@
+/*====================================================================*
+ -  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  checkerboard.c
+ * <pre>
+ *
+ *    Find the checker corners where 4 squares come together
+ *      PIX            *pixFindCheckerboardCorners()
+ *
+ *    Generate the hit-miss sels
+ *      static SELA    *makeCheckerboardCornerSela()
+ *      static PIXA    *makeCheckerboardCornerPixa()
+ *
+ * The functions in this file locate the corners where four squares
+ * in a checkerboard come together.  With a perfectly aligned checkerboard,
+ * the solution is trivial: take the union of two hit-miss transforms (HMTs),
+ * each having a simple diagonal structuring element (sel).  The two
+ * sels can be generated from strings such as these, using
+ * selCreateFromString():
+ *
+ *  static const char *str1 = "o     x"
+ *                            "       "
+ *                            "       "
+ *                            "   C   "
+ *                            "       "
+ *                            "       "
+ *                            "x     o";
+ *  static const char *str2 = "x     o"
+ *                            "       "
+ *                            "       "
+ *                            "   C   "
+ *                            "       "
+ *                            "       "
+ *                            "o     x";
+ *
+ * A more interesting problem is to consider the checkerboard viewed from
+ * some arbitrary angle and orientation from the normal.  The method
+ * developed here works for a camera located within a cone with an opening
+ * half-angle of about 45 degrees, and with its axis along the normal
+ * to the checkerboard.
+ *
+ * See prog/checkerboard_reg.c for usage.
+ *
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+    /* Static helpers */
+static SELA *makeCheckerboardCornerSela(l_int32 size, l_int32 dilation,
+                                        l_int32 nsels, PIXA *pixadb);
+static PIXA *makeCheckerboardCornerPixa(l_int32 size, l_int32 dilation,
+                                        l_int32 nsels);
+
+static const char selnames[64] = "s_diag1 s_diag2 s_cross1 s_cross2";
+
+/*!
+ * \brief   pixFindCheckerboardCorner()
+ *
+ * \param[in]    pixs           of checkerboard
+ * \param[in]    size           size of HMT sel; >= 7, typ. 15; 0 for default
+ * \param[in]    dilation       size of hit and miss squares; typ. 1 or 3; max 5
+ * \param[in]    nsels          number to use (either 2 or 4)
+ * \param[out]   ppix_corners   [optional] 1 bpp pix giving corner locations
+ * \param[out]   ppta_corners   [optional] pta giving corner locations
+ * \param[in]    pixadb         [optional] pass in pre-allocated
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Use %nsels = 4 if the checkerboard may be rotated by more
+ *          than 20 deg.
+ *      (2) The values of %size and %dilation that can be used depend on
+ *          the square sizes.  Nominal values here are for squares of
+ *          size 30 to 50.  In general, because of the viewing angle
+ *          of the camera, the "squares" will appear approximately
+ *          as a rotated rectangle.
+ *      (3) The outputs pix_corners and pta_corners are optional.
+ * </pre>
+ */
+l_ok
+pixFindCheckerboardCorners(PIX     *pixs,
+                           l_int32  size,
+                           l_int32  dilation,
+                           l_int32  nsels,
+                           PIX    **ppix_corners,
+                           PTA    **ppta_corners,
+                           PIXA    *pixadb)
+{
+BOXA    *boxa1;
+PIX     *pix1, *pix2, *pix3;
+PTA     *pta1;
+SEL     *sel;
+SELA    *sela;
+
+    if (ppix_corners) *ppix_corners = NULL;
+    if (ppta_corners) *ppta_corners = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", __func__, 1);
+    if (size <= 0) size = 7;
+    if (size < 7)
+        return ERROR_INT("size too small", __func__, 1);
+    if (dilation < 1 || dilation > 5)
+        return ERROR_INT("dilation not in [1 ...5]", __func__, 1);
+    if (nsels != 2 && nsels != 4)
+        return ERROR_INT("nsels not 2 or 4", __func__, 1);
+
+        /* Generate the hit-miss sels for finding corners */
+    sela = makeCheckerboardCornerSela(size, dilation, nsels, pixadb);
+    if (!sela)
+        return ERROR_INT("sela not made", __func__, 1);
+    if (pixadb) {
+        pix1 = selaDisplayInPix(sela, 15, 3, 15, 2);
+        pixaAddPix(pixadb, pix1, L_INSERT);
+    }
+
+        /* Do the hit-miss transform to find corner locations */
+    pix1 = pixUnionOfMorphOps(pixs, sela, L_MORPH_HMT);
+    if (pixadb) pixaAddPix(pixadb, pix1, L_CLONE);
+    selaDestroy(&sela);
+
+        /* Remove large noise c.c. */
+    pix2 = pixSelectBySize(pix1, size, size, 8, L_SELECT_IF_BOTH,
+                           L_SELECT_IF_LTE, NULL);
+    if (pixadb) pixaAddPix(pixadb, pix2, L_CLONE);
+
+        /* Thin remaining c.c. */
+    pix3 = pixThinConnected(pix2, L_THIN_FG, 8, 0);
+    if (pixadb) pixaAddPix(pixadb, pix3, L_CLONE);
+
+        /* Extract the location of the center of each component */
+    boxa1 = pixConnCompBB(pix3, 8);
+    pta1 = boxaExtractCorners(boxa1, L_BOX_CENTER);
+    boxaDestroy(&boxa1);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    if (pixadb) {  /* show the result as colored plus signs on the input */
+        sel = selMakePlusSign(15, 2);
+        pix1 = pixDisplaySelectedPixels(pixs, pix3, sel, 0xff000000);
+        pixaAddPix(pixadb, pix1, L_INSERT);
+        selDestroy(&sel);
+    }
+
+    if (ppix_corners)
+        *ppix_corners = pix3;
+    else
+        pixDestroy(&pix3);
+    if (ppta_corners)
+        *ppta_corners = pta1;
+    else
+        ptaDestroy(&pta1);
+    return 0;
+}
+
+
+/*!
+ * \brief   makeCheckerboardCornerSela()
+ *
+ * \param[in]    size         size of HMT sel; >= 7, typ. 15; 0 for default
+ * \param[in]    dilation     size of hit and miss squares; typ. 1 or 3; max 5
+ * \param[in]    nsels        number to use (either 2 or 4)
+ * \param[in]    pixadb       [optional] pass in pre-allocated
+ * \return  sela   hit-miss sels for finding corners, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Use 4 sels if the checkerboard may be rotated by more than 20 deg.
+ * </pre>
+ */
+static SELA *
+makeCheckerboardCornerSela(l_int32  size,
+                           l_int32  dilation,
+                           l_int32  nsels,
+                           PIXA    *pixadb)
+{
+PIX     *pix1;
+PIXA    *pixa1;
+SARRAY  *sa;
+SELA    *sela;
+
+    if (size <= 0) size = 7;
+    if (size < 7)
+        return (SELA *)ERROR_PTR("size too small", __func__, NULL);
+    if (dilation < 1 || dilation > 5)
+        return (SELA *)ERROR_PTR("dilation not in [1 ...5]", __func__, NULL);
+    if (nsels != 2 && nsels != 4)
+        return (SELA *)ERROR_PTR("nsels not 2 or 4", __func__, NULL);
+
+    if ((pixa1 = makeCheckerboardCornerPixa(size, dilation, nsels)) == NULL)
+        return (SELA *)ERROR_PTR("pixa for sels not made", __func__, NULL);
+    if (pixadb) {
+        pix1 = pixaDisplayTiledInColumns(pixa1, 4, 8.0, 15, 2);
+        pixaAddPix(pixadb, pix1, L_INSERT);
+    }
+    sa = sarrayCreateWordsFromString(selnames);
+    sela = selaCreateFromColorPixa(pixa1, sa);
+    pixaDestroy(&pixa1);
+    sarrayDestroy(&sa);
+    if (!sela)
+        return (SELA *)ERROR_PTR("sela not made", __func__, NULL);
+    return sela;
+}
+
+
+/*!
+ * \brief   makeCheckerboardCornerPixa()
+ *
+ * \param[in]    size         size of HMT sel; >= 7, typ. 15; 0 for default
+ * \param[in]    dilation     size of hit and miss squares; typ. 1 or 3; max 5
+ * \param[in]    nsels        number to use (either 2 or 4)
+ * \return  pixa   representing hit-miss sels for finding corners, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Each pix can be used to generate a hit-miss sel, using the
+ *          function selCreateFromColorPix().  See that function for the
+ *          use of color and gray pixels to encode the hits, misses and
+ *          center in the structuring element.
+ * </pre>
+ */
+static PIXA *
+makeCheckerboardCornerPixa(l_int32  size,
+                           l_int32  dilation,
+                           l_int32  nsels)
+{
+PIX   *pix1, *pix2, *pix3;
+PIXA  *pixa1;
+
+    pixa1 = pixaCreate(4);
+
+        /* Represent diagonal neg slope hits and pos slope misses */
+    pix1 = pixCreate(size, size, 32);
+    pixSetAll(pix1);
+    pix2 = pixCreate(size, size, 1);  /* slope -1 line (2 pixel) mask */
+    pixSetPixel(pix2, 1, 1, 1);  /* UL corner */
+    pixSetPixel(pix2, size - 2, size - 2, 1);  /* LR corner */
+    if (dilation > 1)
+        pixDilateBrick(pix2, pix2, dilation, dilation);  /* dilate each pixel */
+    pixSetMasked(pix1, pix2, 0x00ff0000);  /* green hit */
+    pix3 = pixRotate90(pix2, 1);  /* slope +1 line (2 pixel) mask */
+    pixSetMasked(pix1, pix3, 0xff000000);  /* red miss */
+    pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128);  /* gray center */
+    pixaAddPix(pixa1, pix1, L_INSERT);
+
+        /* Represent diagonal pos slope hits and neg slope misses */
+    pix1 = pixCreate(size, size, 32);
+    pixSetAll(pix1);
+    pixSetMasked(pix1, pix2, 0xff000000);  /* red hit */
+    pixSetMasked(pix1, pix3, 0x00ff0000);  /* green miss */
+    pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128);  /* gray center */
+    pixaAddPix(pixa1, pix1, L_INSERT);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+
+    if (nsels == 2)
+        return pixa1;
+
+        /* Represent cross: vertical hits and horizontal misses */
+    pix1 = pixCreate(size, size, 32);
+    pixSetAll(pix1);
+    pix2 = pixCreate(size, size, 1);  /* vertical line (2 pixel) mask */
+    pixSetPixel(pix2, size / 2, 1, 1);
+    pixSetPixel(pix2, size / 2, size - 2, 1);
+    if (dilation > 1)
+        pixDilateBrick(pix2, pix2, dilation, dilation);  /* dilate each pixel */
+    pixSetMasked(pix1, pix2, 0x00ff0000);  /* green hit */
+    pix3 = pixRotate90(pix2, 1);  /* horizontal line (2 pixel) mask */
+    pixSetMasked(pix1, pix3, 0xff000000);  /* red miss */
+    pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128);  /* gray center */
+    pixaAddPix(pixa1, pix1, L_INSERT);
+
+        /* Represent cross: horizontal hits and vertical misses */
+    pix1 = pixCreate(size, size, 32);
+    pixSetAll(pix1);
+    pixSetMasked(pix1, pix3, 0x00ff0000);  /* green hit */
+    pixSetMasked(pix1, pix2, 0xff000000);  /* red miss */
+    pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128);  /* gray center */
+    pixaAddPix(pixa1, pix1, L_INSERT);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+
+    return pixa1;
+}
+