diff mupdf-source/thirdparty/leptonica/src/kernel.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/kernel.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1239 @@
+/*====================================================================*
+ -  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 kernel.c
+ * <pre>
+ *
+ *      Basic operations on kernels for image convolution
+ *
+ *         Create/destroy/copy
+ *            L_KERNEL   *kernelCreate()
+ *            void        kernelDestroy()
+ *            L_KERNEL   *kernelCopy()
+ *
+ *         Accessors:
+ *            l_int32     kernelGetElement()
+ *            l_int32     kernelSetElement()
+ *            l_int32     kernelGetParameters()
+ *            l_int32     kernelSetOrigin()
+ *            l_int32     kernelGetSum()
+ *            l_int32     kernelGetMinMax()
+ *
+ *         Normalize/invert
+ *            L_KERNEL   *kernelNormalize()
+ *            L_KERNEL   *kernelInvert()
+ *
+ *         Helper function
+ *            l_float32 **create2dFloatArray()
+ *
+ *         Serialized I/O
+ *            L_KERNEL   *kernelRead()
+ *            L_KERNEL   *kernelReadStream()
+ *            l_int32     kernelWrite()
+ *            l_int32     kernelWriteStream()
+ *
+ *         Making a kernel from a compiled string
+ *            L_KERNEL   *kernelCreateFromString()
+ *
+ *         Making a kernel from a simple file format
+ *            L_KERNEL   *kernelCreateFromFile()
+ *
+ *         Making a kernel from a Pix
+ *            L_KERNEL   *kernelCreateFromPix()
+ *
+ *         Display a kernel in a pix
+ *            PIX        *kernelDisplayInPix()
+ *
+ *         Parse string to extract numbers
+ *            NUMA       *parseStringForNumbers()
+ *
+ *      Simple parametric kernels
+ *            L_KERNEL   *makeFlatKernel()
+ *            L_KERNEL   *makeGaussianKernel()
+ *            L_KERNEL   *makeGaussianKernelSep()
+ *            L_KERNEL   *makeDoGKernel()
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+    /* Array size must be > 0 and not larger than this */
+static const l_uint32  MaxArraySize = 100000;
+
+/*------------------------------------------------------------------------*
+ *                           Create / Destroy                             *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief   kernelCreate()
+ *
+ * \param[in]    height, width
+ * \return  kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) kernelCreate() initializes all values to 0.
+ *      (2) After this call, (cy,cx) and nonzero data values must be
+ *          assigned.
+ *      (2) The number of kernel elements must be less than 2^29.
+ * </pre>
+ */
+L_KERNEL *
+kernelCreate(l_int32  height,
+             l_int32  width)
+{
+l_uint64   size64;
+L_KERNEL  *kel;
+
+    if (width <= 0)
+        return (L_KERNEL *)ERROR_PTR("width must be > 0", __func__, NULL);
+    if (height <= 0)
+        return (L_KERNEL *)ERROR_PTR("height must be > 0", __func__, NULL);
+
+        /* Avoid overflow in malloc arg */
+    size64 = (l_uint64)width * (l_uint64)height;
+    if (size64 >= (1LL << 29)) {
+        L_ERROR("requested width = %d, height = %d\n", __func__, width, height);
+        return (L_KERNEL *)ERROR_PTR("size >= 2^29", __func__, NULL);
+    }
+
+    kel = (L_KERNEL *)LEPT_CALLOC(1, sizeof(L_KERNEL));
+    kel->sy = height;
+    kel->sx = width;
+    if ((kel->data = create2dFloatArray(height, width)) == NULL) {
+        LEPT_FREE(kel);
+        return (L_KERNEL *)ERROR_PTR("data not allocated", __func__, NULL);
+    }
+    return kel;
+}
+
+
+/*!
+ * \brief   kernelDestroy()
+ *
+ * \param[in,out]   pkel    will be set to null before returning
+ * \return  void
+ */
+void
+kernelDestroy(L_KERNEL  **pkel)
+{
+l_int32    i;
+L_KERNEL  *kel;
+
+    if (pkel == NULL)  {
+        L_WARNING("ptr address is NULL!\n", __func__);
+        return;
+    }
+    if ((kel = *pkel) == NULL)
+        return;
+
+    for (i = 0; i < kel->sy; i++)
+        LEPT_FREE(kel->data[i]);
+    LEPT_FREE(kel->data);
+    LEPT_FREE(kel);
+    *pkel = NULL;
+}
+
+
+/*!
+ * \brief   kernelCopy()
+ *
+ * \param[in]    kels    source kernel
+ * \return  keld   copy of kels, or NULL on error
+ */
+L_KERNEL *
+kernelCopy(L_KERNEL  *kels)
+{
+l_int32    i, j, sx, sy, cx, cy;
+L_KERNEL  *keld;
+
+    if (!kels)
+        return (L_KERNEL *)ERROR_PTR("kels not defined", __func__, NULL);
+
+    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
+    if ((keld = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("keld not made", __func__, NULL);
+    keld->cy = cy;
+    keld->cx = cx;
+    for (i = 0; i < sy; i++)
+        for (j = 0; j < sx; j++)
+            keld->data[i][j] = kels->data[i][j];
+
+    return keld;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                               Accessors                              *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelGetElement()
+ *
+ * \param[in]    kel
+ * \param[in]    row
+ * \param[in]    col
+ * \param[out]   pval
+ * \return  0 if OK; 1 on error
+ */
+l_ok
+kernelGetElement(L_KERNEL   *kel,
+                 l_int32     row,
+                 l_int32     col,
+                 l_float32  *pval)
+{
+    if (!pval)
+        return ERROR_INT("&val not defined", __func__, 1);
+    *pval = 0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", __func__, 1);
+    if (row < 0 || row >= kel->sy)
+        return ERROR_INT("kernel row out of bounds", __func__, 1);
+    if (col < 0 || col >= kel->sx)
+        return ERROR_INT("kernel col out of bounds", __func__, 1);
+
+    *pval = kel->data[row][col];
+    return 0;
+}
+
+
+/*!
+ * \brief   kernelSetElement()
+ *
+ * \param[in]    kel kernel
+ * \param[in]    row
+ * \param[in]    col
+ * \param[in]    val
+ * \return  0 if OK; 1 on error
+ */
+l_ok
+kernelSetElement(L_KERNEL  *kel,
+                 l_int32    row,
+                 l_int32    col,
+                 l_float32  val)
+{
+    if (!kel)
+        return ERROR_INT("kel not defined", __func__, 1);
+    if (row < 0 || row >= kel->sy)
+        return ERROR_INT("kernel row out of bounds", __func__, 1);
+    if (col < 0 || col >= kel->sx)
+        return ERROR_INT("kernel col out of bounds", __func__, 1);
+
+    kel->data[row][col] = val;
+    return 0;
+}
+
+
+/*!
+ * \brief   kernelGetParameters()
+ *
+ * \param[in]    kel                  kernel
+ * \param[out]   psy, psx, pcy, pcx   [optional] each can be null
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+kernelGetParameters(L_KERNEL  *kel,
+                    l_int32   *psy,
+                    l_int32   *psx,
+                    l_int32   *pcy,
+                    l_int32   *pcx)
+{
+    if (psy) *psy = 0;
+    if (psx) *psx = 0;
+    if (pcy) *pcy = 0;
+    if (pcx) *pcx = 0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", __func__, 1);
+    if (psy) *psy = kel->sy;
+    if (psx) *psx = kel->sx;
+    if (pcy) *pcy = kel->cy;
+    if (pcx) *pcx = kel->cx;
+    return 0;
+}
+
+
+/*!
+ * \brief   kernelSetOrigin()
+ *
+ * \param[in]    kel       kernel
+ * \param[in]    cy, cx
+ * \return  0 if OK; 1 on error
+ */
+l_ok
+kernelSetOrigin(L_KERNEL  *kel,
+                l_int32    cy,
+                l_int32    cx)
+{
+    if (!kel)
+        return ERROR_INT("kel not defined", __func__, 1);
+    kel->cy = cy;
+    kel->cx = cx;
+    return 0;
+}
+
+
+/*!
+ * \brief   kernelGetSum()
+ *
+ * \param[in]    kel      kernel
+ * \param[out]   psum     sum of all kernel values
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+kernelGetSum(L_KERNEL   *kel,
+             l_float32  *psum)
+{
+l_int32    sx, sy, i, j;
+
+    if (!psum)
+        return ERROR_INT("&sum not defined", __func__, 1);
+    *psum = 0.0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", __func__, 1);
+
+    kernelGetParameters(kel, &sy, &sx, NULL, NULL);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            *psum += kel->data[i][j];
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   kernelGetMinMax()
+ *
+ * \param[in]    kel      kernel
+ * \param[out]   pmin     [optional] minimum value
+ * \param[out]   pmax     [optional] maximum value
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+kernelGetMinMax(L_KERNEL   *kel,
+                l_float32  *pmin,
+                l_float32  *pmax)
+{
+l_int32    sx, sy, i, j;
+l_float32  val, minval, maxval;
+
+    if (!pmin && !pmax)
+        return ERROR_INT("neither &min nor &max defined", __func__, 1);
+    if (pmin) *pmin = 0.0;
+    if (pmax) *pmax = 0.0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", __func__, 1);
+
+    kernelGetParameters(kel, &sy, &sx, NULL, NULL);
+    minval = 10000000.0;
+    maxval = -10000000.0;
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            val = kel->data[i][j];
+            if (val < minval)
+                minval = val;
+            if (val > maxval)
+                maxval = val;
+        }
+    }
+    if (pmin)
+        *pmin = minval;
+    if (pmax)
+        *pmax = maxval;
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                          Normalize/Invert                            *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelNormalize()
+ *
+ * \param[in]    kels      source kel, to be normalized
+ * \param[in]    normsum   desired sum of elements in keld
+ * \return  keld   normalized version of kels, or NULL on error
+ *                 or if sum of elements is very close to 0)
+ *
+ * <pre>
+ * Notes:
+ *      (1) If the sum of kernel elements is close to 0, do not
+ *          try to calculate the normalized kernel.  Instead,
+ *          return a copy of the input kernel, with a warning.
+ * </pre>
+ */
+L_KERNEL *
+kernelNormalize(L_KERNEL  *kels,
+                l_float32  normsum)
+{
+l_int32    i, j, sx, sy, cx, cy;
+l_float32  sum, factor;
+L_KERNEL  *keld;
+
+    if (!kels)
+        return (L_KERNEL *)ERROR_PTR("kels not defined", __func__, NULL);
+
+    kernelGetSum(kels, &sum);
+    if (L_ABS(sum) < 0.00001) {
+        L_WARNING("null sum; not normalizing; returning a copy\n", __func__);
+        return kernelCopy(kels);
+    }
+
+    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
+    if ((keld = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("keld not made", __func__, NULL);
+    keld->cy = cy;
+    keld->cx = cx;
+
+    factor = normsum / sum;
+    for (i = 0; i < sy; i++)
+        for (j = 0; j < sx; j++)
+            keld->data[i][j] = factor * kels->data[i][j];
+
+    return keld;
+}
+
+
+/*!
+ * \brief   kernelInvert()
+ *
+ * \param[in]    kels   source kel, to be inverted
+ * \return  keld   spatially inverted, about the origin, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) For convolution, the kernel is spatially inverted before
+ *          a "correlation" operation is done between the kernel and the image.
+ * </pre>
+ */
+L_KERNEL *
+kernelInvert(L_KERNEL  *kels)
+{
+l_int32    i, j, sx, sy, cx, cy;
+L_KERNEL  *keld;
+
+    if (!kels)
+        return (L_KERNEL *)ERROR_PTR("kels not defined", __func__, NULL);
+
+    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
+    if ((keld = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("keld not made", __func__, NULL);
+    keld->cy = sy - 1 - cy;
+    keld->cx = sx - 1 - cx;
+
+    for (i = 0; i < sy; i++)
+        for (j = 0; j < sx; j++)
+            keld->data[i][j] = kels->data[sy - 1 - i][sx - 1 - j];
+
+    return keld;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Helper function                           *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   create2dFloatArray()
+ *
+ * \param[in]    sy   rows == height
+ * \param[in]    sx   columns == width
+ * \return  doubly indexed array i.e., an array of sy row pointers,
+ *              each of which points to an array of sx floats
+ *
+ * <pre>
+ * Notes:
+ *      (1) The array[%sy][%sx] is indexed in standard "matrix notation",
+ *          with the row index first.
+ *      (2) The caller kernelCreate() limits the size to < 2^29 pixels.
+ * </pre>
+ */
+l_float32 **
+create2dFloatArray(l_int32  sy,
+                   l_int32  sx)
+{
+l_int32      i;
+l_float32  **array;
+
+    if (sx <= 0 || sx > MaxArraySize)
+        return (l_float32 **)ERROR_PTR("sx out of bounds", __func__, NULL);
+    if (sy <= 0 || sy > MaxArraySize)
+        return (l_float32 **)ERROR_PTR("sy out of bounds", __func__, NULL);
+
+    array = (l_float32 **)LEPT_CALLOC(sy, sizeof(l_float32 *));
+    for (i = 0; i < sy; i++)
+        array[i] = (l_float32 *)LEPT_CALLOC(sx, sizeof(l_float32));
+    return array;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Kernel serialized I/O                     *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelRead()
+ *
+ * \param[in]    fname    filename
+ * \return  kernel, or NULL on error
+ */
+L_KERNEL *
+kernelRead(const char  *fname)
+{
+FILE      *fp;
+L_KERNEL  *kel;
+
+    if (!fname)
+        return (L_KERNEL *)ERROR_PTR("fname not defined", __func__, NULL);
+
+    if ((fp = fopenReadStream(fname)) == NULL)
+        return (L_KERNEL *)ERROR_PTR_1("stream not opened",
+                                       fname, __func__, NULL);
+    if ((kel = kernelReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_KERNEL *)ERROR_PTR_1("kel not returned",
+                                       fname, __func__, NULL);
+    }
+    fclose(fp);
+
+    return kel;
+}
+
+
+/*!
+ * \brief   kernelReadStream()
+ *
+ * \param[in]    fp    file stream
+ * \return  kernel, or NULL on error
+ */
+L_KERNEL *
+kernelReadStream(FILE  *fp)
+{
+l_int32    sy, sx, cy, cx, i, j, ret, version, ignore;
+L_KERNEL  *kel;
+
+    if (!fp)
+        return (L_KERNEL *)ERROR_PTR("stream not defined", __func__, NULL);
+
+    ret = fscanf(fp, "  Kernel Version %d\n", &version);
+    if (ret != 1)
+        return (L_KERNEL *)ERROR_PTR("not a kernel file", __func__, NULL);
+    if (version != KERNEL_VERSION_NUMBER)
+        return (L_KERNEL *)ERROR_PTR("invalid kernel version", __func__, NULL);
+
+    if (fscanf(fp, "  sy = %d, sx = %d, cy = %d, cx = %d\n",
+            &sy, &sx, &cy, &cx) != 4)
+        return (L_KERNEL *)ERROR_PTR("dimensions not read", __func__, NULL);
+    if (sx > MaxArraySize || sy > MaxArraySize) {
+        L_ERROR("sx = %d or sy = %d > %d\n", __func__, sx, sy, MaxArraySize);
+        return NULL;
+    }
+    if ((kel = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL);
+    kernelSetOrigin(kel, cy, cx);
+
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++)
+            ignore = fscanf(fp, "%15f", &kel->data[i][j]);
+        ignore = fscanf(fp, "\n");
+    }
+    ignore = fscanf(fp, "\n");
+
+    return kel;
+}
+
+
+/*!
+ * \brief   kernelWrite()
+ *
+ * \param[in]    fname    output file
+ * \param[in]    kel      kernel
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+kernelWrite(const char  *fname,
+            L_KERNEL    *kel)
+{
+FILE  *fp;
+
+    if (!fname)
+        return ERROR_INT("fname not defined", __func__, 1);
+    if (!kel)
+        return ERROR_INT("kel not defined", __func__, 1);
+
+    if ((fp = fopenWriteStream(fname, "wb")) == NULL)
+        return ERROR_INT_1("stream not opened", fname, __func__, 1);
+    kernelWriteStream(fp, kel);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ * \brief   kernelWriteStream()
+ *
+ * \param[in]    fp    file stream
+ * \param[in]    kel
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+kernelWriteStream(FILE      *fp,
+                  L_KERNEL  *kel)
+{
+l_int32  sx, sy, cx, cy, i, j;
+
+    if (!fp)
+        return ERROR_INT("stream not defined", __func__, 1);
+    if (!kel)
+        return ERROR_INT("kel not defined", __func__, 1);
+    kernelGetParameters(kel, &sy, &sx, &cy, &cx);
+
+    fprintf(fp, "  Kernel Version %d\n", KERNEL_VERSION_NUMBER);
+    fprintf(fp, "  sy = %d, sx = %d, cy = %d, cx = %d\n", sy, sx, cy, cx);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++)
+            fprintf(fp, "%15.4f", kel->data[i][j]);
+        fprintf(fp, "\n");
+    }
+    fprintf(fp, "\n");
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                 Making a kernel from a compiled string               *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelCreateFromString()
+ *
+ * \param[in]    h, w     height, width
+ * \param[in]    cy, cx   origin
+ * \param[in]    kdata
+ * \return  kernel of the given size, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The data is an array of chars, in row-major order, giving
+ *          space separated integers in the range [-255 ... 255].
+ *      (2) The only other formatting limitation is that you must
+ *          leave space between the last number in each row and
+ *          the double-quote.  If possible, it's also nice to have each
+ *          line in the string represent a line in the kernel; e.g.,
+ *              static const char *kdata =
+ *                  " 20   50   20 "
+ *                  " 70  140   70 "
+ *                  " 20   50   20 ";
+ * </pre>
+ */
+L_KERNEL *
+kernelCreateFromString(l_int32      h,
+                       l_int32      w,
+                       l_int32      cy,
+                       l_int32      cx,
+                       const char  *kdata)
+{
+l_int32    n, i, j, index;
+l_float32  val;
+L_KERNEL  *kel;
+NUMA      *na;
+
+    if (h < 1)
+        return (L_KERNEL *)ERROR_PTR("height must be > 0", __func__, NULL);
+    if (w < 1)
+        return (L_KERNEL *)ERROR_PTR("width must be > 0", __func__, NULL);
+    if (cy < 0 || cy >= h)
+        return (L_KERNEL *)ERROR_PTR("cy invalid", __func__, NULL);
+    if (cx < 0 || cx >= w)
+        return (L_KERNEL *)ERROR_PTR("cx invalid", __func__, NULL);
+
+    kel = kernelCreate(h, w);
+    kernelSetOrigin(kel, cy, cx);
+    na = parseStringForNumbers(kdata, " \t\n");
+    n = numaGetCount(na);
+    if (n != w * h) {
+        kernelDestroy(&kel);
+        numaDestroy(&na);
+        lept_stderr("w = %d, h = %d, num ints = %d\n", w, h, n);
+        return (L_KERNEL *)ERROR_PTR("invalid integer data", __func__, NULL);
+    }
+
+    index = 0;
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            numaGetFValue(na, index, &val);
+            kernelSetElement(kel, i, j, val);
+            index++;
+        }
+    }
+
+    numaDestroy(&na);
+    return kel;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                Making a kernel from a simple file format             *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelCreateFromFile()
+ *
+ * \param[in]    filename
+ * \return  kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The file contains, in the following order:
+ *           ~ Any number of comment lines starting with '#' are ignored
+ *           ~ The height and width of the kernel
+ *           ~ The y and x values of the kernel origin
+ *           ~ The kernel data, formatted as lines of numbers (integers
+ *             or floats) for the kernel values in row-major order,
+ *             and with no other punctuation.
+ *             (Note: this differs from kernelCreateFromString(),
+ *             where each line must begin and end with a double-quote
+ *             to tell the compiler it's part of a string.)
+ *           ~ The kernel specification ends when a blank line,
+ *             a comment line, or the end of file is reached.
+ *      (2) All lines must be left-justified.
+ *      (3) See kernelCreateFromString() for a description of the string
+ *          format for the kernel data.  As an example, here are the lines
+ *          of a valid kernel description file  In the file, all lines
+ *          are left-justified:
+ * \code
+ *                    # small 3x3 kernel
+ *                    3 3
+ *                    1 1
+ *                    25.5   51    24.3
+ *                    70.2  146.3  73.4
+ *                    20     50.9  18.4
+ * \endcode
+ * </pre>
+ */
+L_KERNEL *
+kernelCreateFromFile(const char  *filename)
+{
+char      *filestr, *line;
+l_int32    nlines, i, j, first, index, w, h, cx, cy, n;
+l_float32  val;
+size_t     size;
+NUMA      *na, *nat;
+SARRAY    *sa;
+L_KERNEL  *kel;
+
+    if (!filename)
+        return (L_KERNEL *)ERROR_PTR("filename not defined", __func__, NULL);
+
+    if ((filestr = (char *)l_binaryRead(filename, &size)) == NULL)
+        return (L_KERNEL *)ERROR_PTR_1("file not found",
+                                       filename, __func__, NULL);
+    if (size == 0) {
+        LEPT_FREE(filestr);
+        return (L_KERNEL *)ERROR_PTR_1("file is empty",
+                                       filename, __func__, NULL);
+    }
+
+    sa = sarrayCreateLinesFromString(filestr, 1);
+    LEPT_FREE(filestr);
+    nlines = sarrayGetCount(sa);
+
+        /* Find the first data line. */
+    for (i = 0, first = 0; i < nlines; i++) {
+        line = sarrayGetString(sa, i, L_NOCOPY);
+        if (line[0] != '#') {
+            first = i;
+            break;
+        }
+    }
+
+        /* Find the kernel dimensions and origin location. */
+    line = sarrayGetString(sa, first, L_NOCOPY);
+    if (sscanf(line, "%d %d", &h, &w) != 2) {
+        sarrayDestroy(&sa);
+        return (L_KERNEL *)ERROR_PTR("error reading h,w", __func__, NULL);
+    }
+    if (h > MaxArraySize || w > MaxArraySize) {
+        L_ERROR("h = %d or w = %d > %d\n", __func__, h, w, MaxArraySize);
+        sarrayDestroy(&sa);
+        return NULL;
+    }
+    line = sarrayGetString(sa, first + 1, L_NOCOPY);
+    if (sscanf(line, "%d %d", &cy, &cx) != 2) {
+        sarrayDestroy(&sa);
+        return (L_KERNEL *)ERROR_PTR("error reading cy,cx", __func__, NULL);
+    }
+
+        /* Extract the data.  This ends when we reach eof, or when we
+         * encounter a line of data that is either a null string or
+         * contains just a newline. */
+    na = numaCreate(0);
+    for (i = first + 2; i < nlines; i++) {
+        line = sarrayGetString(sa, i, L_NOCOPY);
+        if (line[0] == '\0' || line[0] == '\n' || line[0] == '#')
+            break;
+        nat = parseStringForNumbers(line, " \t\n");
+        numaJoin(na, nat, 0, -1);
+        numaDestroy(&nat);
+    }
+    sarrayDestroy(&sa);
+
+    n = numaGetCount(na);
+    if (n != w * h) {
+        numaDestroy(&na);
+        lept_stderr("w = %d, h = %d, num ints = %d\n", w, h, n);
+        return (L_KERNEL *)ERROR_PTR("invalid integer data", __func__, NULL);
+    }
+
+    kel = kernelCreate(h, w);
+    kernelSetOrigin(kel, cy, cx);
+    index = 0;
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            numaGetFValue(na, index, &val);
+            kernelSetElement(kel, i, j, val);
+            index++;
+        }
+    }
+
+    numaDestroy(&na);
+    return kel;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Making a kernel from a Pix                     *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelCreateFromPix()
+ *
+ * \param[in]    pix
+ * \param[in]    cy, cx    origin of kernel
+ * \return  kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The origin must be positive and within the dimensions of the pix.
+ * </pre>
+ */
+L_KERNEL *
+kernelCreateFromPix(PIX         *pix,
+                    l_int32      cy,
+                    l_int32      cx)
+{
+l_int32    i, j, w, h, d;
+l_uint32   val;
+L_KERNEL  *kel;
+
+    if (!pix)
+        return (L_KERNEL *)ERROR_PTR("pix not defined", __func__, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 8)
+        return (L_KERNEL *)ERROR_PTR("pix not 8 bpp", __func__, NULL);
+    if (cy < 0 || cx < 0 || cy >= h || cx >= w)
+        return (L_KERNEL *)ERROR_PTR("(cy, cx) invalid", __func__, NULL);
+
+    kel = kernelCreate(h, w);
+    kernelSetOrigin(kel, cy, cx);
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            pixGetPixel(pix, j, i, &val);
+            kernelSetElement(kel, i, j, (l_float32)val);
+        }
+    }
+
+    return kel;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Display a kernel in a pix                        *
+ *----------------------------------------------------------------------*/
+/*!
+ * \brief   kernelDisplayInPix()
+ *
+ * \param[in]    kel       kernel
+ * \param[in]    size      of grid interiors; odd; either 1 or a minimum size
+ *                         of 17 is enforced
+ * \param[in]    gthick    grid thickness; either 0 or a minimum size of 2
+ *                         is enforced
+ * \return  pix   display of kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This gives a visual representation of a kernel.
+ *      (2) There are two modes of display:
+ *          (a) Grid lines of minimum width 2, surrounding regions
+ *              representing kernel elements of minimum size 17,
+ *              with a "plus" mark at the kernel origin, or
+ *          (b) A pix without grid lines and using 1 pixel per kernel element.
+ *      (3) For both cases, the kernel absolute value is displayed,
+ *          normalized such that the maximum absolute value is 255.
+ *      (4) Large 2D separable kernels should be used for convolution
+ *          with two 1D kernels.  However, for the bilateral filter,
+ *          the computation time is independent of the size of the
+ *          2D content kernel.
+ * </pre>
+ */
+PIX *
+kernelDisplayInPix(L_KERNEL     *kel,
+                   l_int32       size,
+                   l_int32       gthick)
+{
+l_int32    i, j, w, h, sx, sy, cx, cy, width, x0, y0;
+l_int32    normval;
+l_float32  minval, maxval, max, val, norm;
+PIX       *pixd, *pixt0, *pixt1;
+
+    if (!kel)
+        return (PIX *)ERROR_PTR("kernel not defined", __func__, NULL);
+
+        /* Normalize the max value to be 255 for display */
+    kernelGetParameters(kel, &sy, &sx, &cy, &cx);
+    kernelGetMinMax(kel, &minval, &maxval);
+    max = L_MAX(maxval, -minval);
+    if (max == 0.0)
+        return (PIX *)ERROR_PTR("kernel elements all 0.0", __func__, NULL);
+    norm = 255.f / (l_float32)max;
+
+        /* Handle the 1 element/pixel case; typically with large kernels */
+    if (size == 1 && gthick == 0) {
+        pixd = pixCreate(sx, sy, 8);
+        for (i = 0; i < sy; i++) {
+            for (j = 0; j < sx; j++) {
+                kernelGetElement(kel, i, j, &val);
+                normval = (l_int32)(norm * L_ABS(val));
+                pixSetPixel(pixd, j, i, normval);
+            }
+        }
+        return pixd;
+    }
+
+        /* Enforce the constraints for the grid line version */
+    if (size < 17) {
+        L_WARNING("size < 17; setting to 17\n", __func__);
+        size = 17;
+    }
+    if (size % 2 == 0)
+        size++;
+    if (gthick < 2) {
+        L_WARNING("grid thickness < 2; setting to 2\n", __func__);
+        gthick = 2;
+    }
+
+    w = size * sx + gthick * (sx + 1);
+    h = size * sy + gthick * (sy + 1);
+    pixd = pixCreate(w, h, 8);
+
+        /* Generate grid lines */
+    for (i = 0; i <= sy; i++)
+        pixRenderLine(pixd, 0, gthick / 2 + i * (size + gthick),
+                      w - 1, gthick / 2 + i * (size + gthick),
+                      gthick, L_SET_PIXELS);
+    for (j = 0; j <= sx; j++)
+        pixRenderLine(pixd, gthick / 2 + j * (size + gthick), 0,
+                      gthick / 2 + j * (size + gthick), h - 1,
+                      gthick, L_SET_PIXELS);
+
+        /* Generate mask for each element */
+    pixt0 = pixCreate(size, size, 1);
+    pixSetAll(pixt0);
+
+        /* Generate crossed lines for origin pattern */
+    pixt1 = pixCreate(size, size, 1);
+    width = size / 8;
+    pixRenderLine(pixt1, size / 2, (l_int32)(0.12 * size),
+                           size / 2, (l_int32)(0.88 * size),
+                           width, L_SET_PIXELS);
+    pixRenderLine(pixt1, (l_int32)(0.15 * size), size / 2,
+                           (l_int32)(0.85 * size), size / 2,
+                           width, L_FLIP_PIXELS);
+    pixRasterop(pixt1, size / 2 - width, size / 2 - width,
+                2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0);
+
+        /* Paste the patterns in */
+    y0 = gthick;
+    for (i = 0; i < sy; i++) {
+        x0 = gthick;
+        for (j = 0; j < sx; j++) {
+            kernelGetElement(kel, i, j, &val);
+            normval = (l_int32)(norm * L_ABS(val));
+            pixSetMaskedGeneral(pixd, pixt0, normval, x0, y0);
+            if (i == cy && j == cx)
+                pixPaintThroughMask(pixd, pixt1, x0, y0, 255 - normval);
+            x0 += size + gthick;
+        }
+        y0 += size + gthick;
+    }
+
+    pixDestroy(&pixt0);
+    pixDestroy(&pixt1);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                     Parse string to extract numbers                    *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief   parseStringForNumbers()
+ *
+ * \param[in]    str     string containing numbers; not changed
+ * \param[in]    seps    string of characters that can be used between ints
+ * \return  numa   of numbers found, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *     (1) The numbers can be ints or floats.
+ * </pre>
+ */
+NUMA *
+parseStringForNumbers(const char  *str,
+                      const char  *seps)
+{
+char      *newstr, *head;
+char      *tail = NULL;
+l_float32  val;
+NUMA      *na;
+
+    if (!str)
+        return (NUMA *)ERROR_PTR("str not defined", __func__, NULL);
+
+    newstr = stringNew(str);  /* to enforce const-ness of str */
+    na = numaCreate(0);
+    head = strtokSafe(newstr, seps, &tail);
+    val = atof(head);
+    numaAddNumber(na, val);
+    LEPT_FREE(head);
+    while ((head = strtokSafe(NULL, seps, &tail)) != NULL) {
+        val = atof(head);
+        numaAddNumber(na, val);
+        LEPT_FREE(head);
+    }
+
+    LEPT_FREE(newstr);
+    return na;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Simple parametric kernels                       *
+ *------------------------------------------------------------------------*/
+/*!
+ * \brief   makeFlatKernel()
+ *
+ * \param[in]    height, width
+ * \param[in]    cy, cx          origin of kernel
+ * \return  kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This is the same low-pass filtering kernel that is used
+ *          in the block convolution functions.
+ *      (2) The kernel origin (%cy, %cx) is typically placed as near
+ *          the center of the kernel as possible.  If height and
+ *          width are odd, then using %cy = height / 2 and
+ *          %cx = width / 2 places the origin at the exact center.
+ *      (3) This returns a normalized kernel.
+ * </pre>
+ */
+L_KERNEL *
+makeFlatKernel(l_int32  height,
+               l_int32  width,
+               l_int32  cy,
+               l_int32  cx)
+{
+l_int32    i, j;
+l_float32  normval;
+L_KERNEL  *kel;
+
+    if ((kel = kernelCreate(height, width)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL);
+    kernelSetOrigin(kel, cy, cx);
+    normval = 1.0f / (l_float32)(height * width);
+    for (i = 0; i < height; i++) {
+        for (j = 0; j < width; j++) {
+            kernelSetElement(kel, i, j, normval);
+        }
+    }
+
+    return kel;
+}
+
+
+/*!
+ * \brief   makeGaussianKernel()
+ *
+ * \param[in]    halfh     sy = 2 * halfh + 1
+ * \param[in]    halfw     sx = 2 * halfw + 1
+ * \param[in]    stdev     standard deviation
+ * \param[in]    max       value at (cx,cy)
+ * \return  kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The kernel size (sx, sy) = (2 * %halfw + 1, 2 * %halfh + 1)
+ *      (2) The kernel center (cx, cy) = (%halfw, %halfh).
+ *      (3) %halfw and %halfh are typically equal, and
+ *          are typically several times larger than the standard deviation.
+ *      (4) If pixConvolve() is invoked with normalization (the sum of
+ *          kernel elements = 1.0), use 1.0 for max (or any number that's
+ *          not too small or too large).
+ * </pre>
+ */
+L_KERNEL *
+makeGaussianKernel(l_int32    halfh,
+                   l_int32    halfw,
+                   l_float32  stdev,
+                   l_float32  max)
+{
+l_int32    sx, sy, i, j;
+l_float32  val;
+L_KERNEL  *kel;
+
+    sx = 2 * halfw + 1;
+    sy = 2 * halfh + 1;
+    if ((kel = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL);
+    kernelSetOrigin(kel, halfh, halfw);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            val = expf(-(l_float32)((i - halfh) * (i - halfh) +
+                                    (j - halfw) * (j - halfw)) /
+                        (2. * stdev * stdev));
+            kernelSetElement(kel, i, j, max * val);
+        }
+    }
+
+    return kel;
+}
+
+
+/*!
+ * \brief   makeGaussianKernelSep()
+ *
+ * \param[in]    halfh     sy = 2 * halfh + 1
+ * \param[in]    halfw     sx = 2 * halfw + 1
+ * \param[in]    stdev     standard deviation
+ * \param[in]    max       value at (cx,cy)
+ * \param[out]   pkelx     x part of kernel
+ * \param[out]   pkely     y part of kernel
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) See makeGaussianKernel() for description of input parameters.
+ *      (2) These kernels are constructed so that the result of both
+ *          normalized and un-normalized convolution will be the same
+ *          as when convolving with pixConvolve() using the full kernel.
+ *      (3) The trick for the un-normalized convolution is to have the
+ *          product of the two kernel elements at (cx,cy) be equal to %max,
+ *          not max**2.  That's why %max for kely is 1.0.  If instead
+ *          we use sqrt(%max) for both, the results are slightly less
+ *          accurate, when compared to using the full kernel in
+ *          makeGaussianKernel().
+ * </pre>
+ */
+l_ok
+makeGaussianKernelSep(l_int32    halfh,
+                      l_int32    halfw,
+                      l_float32  stdev,
+                      l_float32  max,
+                      L_KERNEL **pkelx,
+                      L_KERNEL **pkely)
+{
+    if (!pkelx || !pkely)
+        return ERROR_INT("&kelx and &kely not defined", __func__, 1);
+
+    *pkelx = makeGaussianKernel(0, halfw, stdev, max);
+    *pkely = makeGaussianKernel(halfh, 0, stdev, 1.0);
+    return 0;
+}
+
+
+/*!
+ * \brief   makeDoGKernel()
+ *
+ * \param[in]    halfh     sy = 2 * halfh + 1
+ * \param[in]    halfw     sx = 2 * halfw + 1
+ * \param[in]    stdev     standard deviation of narrower gaussian
+ * \param[in]    ratio     of stdev for wide filter to stdev for narrow one
+ * \return  kernel, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The DoG (difference of gaussians) is a wavelet mother
+ *          function with null total sum.  By subtracting two blurred
+ *          versions of the image, it acts as a bandpass filter for
+ *          frequencies passed by the narrow gaussian but stopped
+ *          by the wide one.See:
+ *               http://en.wikipedia.org/wiki/Difference_of_Gaussians
+ *      (2) The kernel size (sx, sy) = (2 * halfw + 1, 2 * halfh + 1).
+ *      (3) The kernel center (cx, cy) = (halfw, halfh).
+ *      (4) %halfw and %halfh are typically equal, and are typically
+ *          several times larger than the standard deviation.
+ *      (5) %ratio is the ratio of standard deviations of the wide
+ *          to narrow gaussian.  It must be >= 1.0; 1.0 is a no-op.
+ *      (6) Because the kernel is a null sum, it must be invoked without
+ *          normalization in pixConvolve().
+ * </pre>
+ */
+L_KERNEL *
+makeDoGKernel(l_int32    halfh,
+              l_int32    halfw,
+              l_float32  stdev,
+              l_float32  ratio)
+{
+l_int32    sx, sy, i, j;
+l_float32  pi, squaredist, highnorm, lownorm, val;
+L_KERNEL  *kel;
+
+    sx = 2 * halfw + 1;
+    sy = 2 * halfh + 1;
+    if ((kel = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL);
+    kernelSetOrigin(kel, halfh, halfw);
+
+    pi = 3.1415926535f;
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            squaredist = (l_float32)((i - halfh) * (i - halfh) +
+                                     (j - halfw) * (j - halfw));
+            highnorm = 1.f / (2 * stdev * stdev);
+            lownorm = highnorm / (ratio * ratio);
+            val = (highnorm / pi) * expf(-(highnorm * squaredist))
+                  - (lownorm / pi) * expf(-(lownorm * squaredist));
+            kernelSetElement(kel, i, j, val);
+        }
+    }
+
+    return kel;
+}