diff mupdf-source/thirdparty/leptonica/src/regutils.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/regutils.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,871 @@
+/*====================================================================*
+ -  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 regutils.c
+ * <pre>
+ *
+ *       Regression test utilities
+ *           l_int32    regTestSetup()
+ *           l_int32    regTestCleanup()
+ *           l_int32    regTestCompareValues()
+ *           l_int32    regTestCompareStrings()
+ *           l_int32    regTestComparePix()
+ *           l_int32    regTestCompareSimilarPix()
+ *           l_int32    regTestCheckFile()
+ *           l_int32    regTestCompareFiles()
+ *           l_int32    regTestWritePixAndCheck()
+ *           l_int32    regTestWriteDataAndCheck()
+ *           char      *regTestGenLocalFilename()
+ *
+ *       Static function
+ *           char      *getRootNameFromArgv0()
+ *
+ *  These functions are for testing and development.  They are not intended
+ *  for use with programs that run in a production environment, such as a
+ *  cloud service with unrestricted access.
+ *
+ *  See regutils.h for how to use this.  Here is a minimal setup:
+ *
+ *  main(int argc, char **argv) {
+ *  ...
+ *  L_REGPARAMS  *rp;
+ *
+ *      if (regTestSetup(argc, argv, &rp))
+ *          return 1;
+ *      ...
+ *      regTestWritePixAndCheck(rp, pix, IFF_PNG);  // 0
+ *      ...
+ *      return regTestCleanup(rp);
+ *  }
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+extern l_int32 NumImageFileFormatExtensions;
+extern const char *ImageFileFormatExtensions[];
+
+static char *getRootNameFromArgv0(const char *argv0);
+
+
+/*--------------------------------------------------------------------*
+ *                      Regression test utilities                     *
+ *--------------------------------------------------------------------*/
+/*!
+ * \brief   regTestSetup()
+ *
+ * \param[in]    argc    from invocation; can be either 1 or 2
+ * \param[in]    argv    to regtest: %argv[1] is one of these:
+ *                       "generate", "compare", "display"
+ * \param[out]   prp     all regression params
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Call this function with the args to the reg test.  The first arg
+ *          is the name of the reg test.  There are three cases:
+ *          Case 1:
+ *              There is either only one arg, or the second arg is "compare".
+ *              This is the mode in which you run a regression test
+ *              (or a set of them), looking for failures and logging
+ *              the results to a file.  The output, which includes
+ *              logging of all reg test failures plus a SUCCESS or
+ *              FAILURE summary for each test, is appended to the file
+ *              "/tmp/lept/reg_results.txt.  For this case, as in Case 2,
+ *              the display field in rp is set to FALSE, preventing
+ *              image display.
+ *          Case 2:
+ *              The second arg is "generate".  This will cause
+ *              generation of new golden files for the reg test.
+ *              The results of the reg test are not recorded, and
+ *              the display field in rp is set to FALSE.
+ *          Case 3:
+ *              The second arg is "display".  The test will run and
+ *              files will be written.  Comparisons with golden files
+ *              will not be carried out, so the only notion of success
+ *              or failure is with tests that do not involve golden files.
+ *              The display field in rp is TRUE, and this is used by
+ *              pixDisplayWithTitle().
+ *      (2) See regutils.h for examples of usage.
+ * </pre>
+ */
+l_ok
+regTestSetup(l_int32        argc,
+             char         **argv,
+             L_REGPARAMS  **prp)
+{
+char         *testname, *vers;
+char          errormsg[64];
+L_REGPARAMS  *rp;
+
+    if (argc != 1 && argc != 2) {
+        snprintf(errormsg, sizeof(errormsg),
+            "Syntax: %s [ [compare] | generate | display ]", argv[0]);
+        return ERROR_INT(errormsg, __func__, 1);
+    }
+
+    if ((testname = getRootNameFromArgv0(argv[0])) == NULL)
+        return ERROR_INT("invalid root", __func__, 1);
+
+    setLeptDebugOK(1);  /* required for testing */
+
+    rp = (L_REGPARAMS *)LEPT_CALLOC(1, sizeof(L_REGPARAMS));
+    *prp = rp;
+    rp->testname = testname;
+    rp->index = -1;  /* increment before each test */
+
+        /* Initialize to true.  A failure in any test is registered
+         * as a failure of the regression test. */
+    rp->success = TRUE;
+
+        /* Make sure the lept/regout subdirectory exists */
+    lept_mkdir("lept/regout");
+
+        /* Only open a stream to a temp file for the 'compare' case */
+    if (argc == 1 || !strcmp(argv[1], "compare")) {
+        rp->mode = L_REG_COMPARE;
+        rp->tempfile = stringNew("/tmp/lept/regout/regtest_output.txt");
+        rp->fp = fopenWriteStream(rp->tempfile, "wb");
+        if (rp->fp == NULL) {
+            rp->success = FALSE;
+            return ERROR_INT_1("stream not opened for tempfile",
+                               rp->tempfile, __func__, 1);
+        }
+    } else if (!strcmp(argv[1], "generate")) {
+        rp->mode = L_REG_GENERATE;
+        lept_mkdir("lept/golden");
+    } else if (!strcmp(argv[1], "display")) {
+        rp->mode = L_REG_DISPLAY;
+        rp->display = TRUE;
+    } else {
+        LEPT_FREE(rp);
+        snprintf(errormsg, sizeof(errormsg),
+            "Syntax: %s [ [generate] | compare | display ]", argv[0]);
+        return ERROR_INT(errormsg, __func__, 1);
+    }
+
+        /* Print out test name and both the leptonica and
+         * image library versions */
+    lept_stderr("\n////////////////////////////////////////////////\n"
+                "////////////////   %s_reg   ///////////////\n"
+                "////////////////////////////////////////////////\n",
+                rp->testname);
+    vers = getLeptonicaVersion();
+    lept_stderr("%s : ", vers);
+    LEPT_FREE(vers);
+    vers = getImagelibVersions();
+    lept_stderr("%s\n", vers);
+    LEPT_FREE(vers);
+
+    rp->tstart = startTimerNested();
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestCleanup()
+ *
+ * \param[in]    rp    regression test parameters
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This copies anything written to the temporary file to the
+ *          output file /tmp/lept/reg_results.txt.
+ * </pre>
+ */
+l_ok
+regTestCleanup(L_REGPARAMS  *rp)
+{
+char     result[512];
+char    *results_file;  /* success/failure output in 'compare' mode */
+char    *text, *message;
+l_int32  retval;
+size_t   nbytes;
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+
+    lept_stderr("Time: %7.3f sec\n", stopTimerNested(rp->tstart));
+
+        /* If generating golden files or running in display mode, release rp */
+    if (!rp->fp) {
+        LEPT_FREE(rp->testname);
+        LEPT_FREE(rp->tempfile);
+        LEPT_FREE(rp);
+        return 0;
+    }
+
+        /* Compare mode: read back data from temp file */
+    fclose(rp->fp);
+    text = (char *)l_binaryRead(rp->tempfile, &nbytes);
+    LEPT_FREE(rp->tempfile);
+    if (!text) {
+        rp->success = FALSE;
+        LEPT_FREE(rp->testname);
+        LEPT_FREE(rp);
+        return ERROR_INT("text not returned", __func__, 1);
+    }
+
+        /* Prepare result message */
+    if (rp->success)
+        snprintf(result, sizeof(result), "SUCCESS: %s_reg\n", rp->testname);
+    else
+        snprintf(result, sizeof(result), "FAILURE: %s_reg\n", rp->testname);
+    message = stringJoin(text, result);
+    LEPT_FREE(text);
+    results_file = stringNew("/tmp/lept/reg_results.txt");
+    fileAppendString(results_file, message);
+    retval = (rp->success) ? 0 : 1;
+    LEPT_FREE(results_file);
+    LEPT_FREE(message);
+
+    LEPT_FREE(rp->testname);
+    LEPT_FREE(rp);
+    return retval;
+}
+
+
+/*!
+ * \brief   regTestCompareValues()
+ *
+ * \param[in]    rp      regtest parameters
+ * \param[in]    val1    typ. the golden value
+ * \param[in]    val2    typ. the value computed
+ * \param[in]    delta   allowed max absolute difference
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ */
+l_ok
+regTestCompareValues(L_REGPARAMS  *rp,
+                     l_float32     val1,
+                     l_float32     val2,
+                     l_float32     delta)
+{
+l_float32  diff;
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+
+    rp->index++;
+    diff = L_ABS(val2 - val1);
+
+        /* Record on failure */
+    if (diff > delta) {
+        if (rp->fp) {
+            fprintf(rp->fp,
+                    "Failure in %s_reg: value comparison for index %d\n"
+                    "difference = %f but allowed delta = %f\n",
+                    rp->testname, rp->index, diff, delta);
+        }
+        lept_stderr("Failure in %s_reg: value comparison for index %d\n"
+                    "difference = %f but allowed delta = %f\n",
+                    rp->testname, rp->index, diff, delta);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestCompareStrings()
+ *
+ * \param[in]    rp        regtest parameters
+ * \param[in]    string1   typ. the expected string
+ * \param[in]    bytes1    size of string1
+ * \param[in]    string2   typ. the computed string
+ * \param[in]    bytes2    size of string2
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ */
+l_ok
+regTestCompareStrings(L_REGPARAMS  *rp,
+                      l_uint8      *string1,
+                      size_t        bytes1,
+                      l_uint8      *string2,
+                      size_t        bytes2)
+{
+l_int32  same;
+char     buf[256];
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+
+    rp->index++;
+    l_binaryCompare(string1, bytes1, string2, bytes2, &same);
+
+        /* Output on failure */
+    if (!same) {
+            /* Write the two strings to file */
+        snprintf(buf, sizeof(buf), "/tmp/lept/regout/string1_%d_%zu",
+                 rp->index, bytes1);
+        l_binaryWrite(buf, "w", string1, bytes1);
+        snprintf(buf, sizeof(buf), "/tmp/lept/regout/string2_%d_%zu",
+                 rp->index, bytes2);
+        l_binaryWrite(buf, "w", string2, bytes2);
+
+            /* Report comparison failure */
+        snprintf(buf, sizeof(buf), "/tmp/lept/regout/string*_%d_*", rp->index);
+        if (rp->fp) {
+            fprintf(rp->fp,
+                    "Failure in %s_reg: string comp for index %d; "
+                    "written to %s\n", rp->testname, rp->index, buf);
+        }
+        lept_stderr("Failure in %s_reg: string comp for index %d; "
+                    "written to %s\n", rp->testname, rp->index, buf);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestComparePix()
+ *
+ * \param[in]    rp            regtest parameters
+ * \param[in]    pix1, pix2    to be tested for equality
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This function compares two pix for equality.  On failure,
+ *          this writes to stderr.
+ * </pre>
+ */
+l_ok
+regTestComparePix(L_REGPARAMS  *rp,
+                  PIX          *pix1,
+                  PIX          *pix2)
+{
+l_int32  same;
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+    if (!pix1 || !pix2) {
+        rp->success = FALSE;
+        return ERROR_INT("pix1 and pix2 not both defined", __func__, 1);
+    }
+
+    rp->index++;
+    pixEqual(pix1, pix2, &same);
+
+        /* Record on failure */
+    if (!same) {
+        if (rp->fp) {
+            fprintf(rp->fp, "Failure in %s_reg: pix comparison for index %d\n",
+                    rp->testname, rp->index);
+        }
+        lept_stderr("Failure in %s_reg: pix comparison for index %d\n",
+                    rp->testname, rp->index);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestCompareSimilarPix()
+ *
+ * \param[in]    rp           regtest parameters
+ * \param[in]    pix1, pix2   to be tested for near equality
+ * \param[in]    mindiff      minimum pixel difference to be counted; > 0
+ * \param[in]    maxfract     maximum fraction of pixels allowed to have
+ *                            diff greater than or equal to mindiff
+ * \param[in]    printstats   use 1 to print normalized histogram to stderr
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in similarity comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This function compares two pix for near equality.  On failure,
+ *          this writes to stderr.
+ *      (2) The pix are similar if the fraction of non-conforming pixels
+ *          does not exceed %maxfract.  Pixels are non-conforming if
+ *          the difference in pixel values equals or exceeds %mindiff.
+ *          Typical values might be %mindiff = 15 and %maxfract = 0.01.
+ *      (3) The input images must have the same size and depth.  The
+ *          pixels for comparison are typically subsampled from the images.
+ *      (4) Normally, use %printstats = 0.  In debugging mode, to see
+ *          the relation between %mindiff and the minimum value of
+ *          %maxfract for success, set this to 1.
+ * </pre>
+ */
+l_ok
+regTestCompareSimilarPix(L_REGPARAMS  *rp,
+                         PIX          *pix1,
+                         PIX          *pix2,
+                         l_int32       mindiff,
+                         l_float32     maxfract,
+                         l_int32       printstats)
+{
+l_int32  w, h, factor, similar;
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+    if (!pix1 || !pix2) {
+        rp->success = FALSE;
+        return ERROR_INT("pix1 and pix2 not both defined", __func__, 1);
+    }
+
+    rp->index++;
+    pixGetDimensions(pix1, &w, &h, NULL);
+    factor = L_MAX(w, h) / 400;
+    factor = L_MAX(1, L_MIN(factor, 4));   /* between 1 and 4 */
+    pixTestForSimilarity(pix1, pix2, factor, mindiff, maxfract, 0.0,
+                         &similar, printstats);
+
+        /* Record on failure */
+    if (!similar) {
+        if (rp->fp) {
+            fprintf(rp->fp,
+                    "Failure in %s_reg: pix similarity comp for index %d\n",
+                    rp->testname, rp->index);
+        }
+        lept_stderr("Failure in %s_reg: pix similarity comp for index %d\n",
+                    rp->testname, rp->index);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestCheckFile()
+ *
+ * \param[in]    rp         regtest parameters
+ * \param[in]    localname  name of output file from reg test
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This function does one of three things, depending on the mode:
+ *           * "generate": makes a "golden" file as a copy of %localname.
+ *           * "compare": compares %localname contents with the golden file
+ *           * "display": this does nothing
+ *      (2) The canonical format of the golden filenames is:
+ *            /tmp/lept/golden/[root of main name]_golden.[index].
+ *                                                       [ext of localname]
+ *          e.g.,
+ *             /tmp/lept/golden/maze_golden.0.png
+ *      (3) The local file can be made in any subdirectory of /tmp/lept,
+ *          including /tmp/lept/regout/.
+ *      (4) It is important to add an extension to the local name, such as
+ *             /tmp/lept/maze/file1.png    (extension ".png")
+ *          because the extension is added to the name of the golden file.
+ * </pre>
+ */
+l_ok
+regTestCheckFile(L_REGPARAMS  *rp,
+                 const char   *localname)
+{
+char    *ext;
+char     namebuf[256];
+l_int32  ret, same, format;
+PIX     *pix1, *pix2;
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+    if (!localname) {
+        rp->success = FALSE;
+        return ERROR_INT("local name not defined", __func__, 1);
+    }
+    if (rp->mode != L_REG_GENERATE && rp->mode != L_REG_COMPARE &&
+        rp->mode != L_REG_DISPLAY) {
+        rp->success = FALSE;
+        return ERROR_INT("invalid mode", __func__, 1);
+    }
+    rp->index++;
+
+        /* If display mode, no generation and no testing */
+    if (rp->mode == L_REG_DISPLAY) return 0;
+
+        /* Generate the golden file name; used in 'generate' and 'compare' */
+    splitPathAtExtension(localname, NULL, &ext);
+    snprintf(namebuf, sizeof(namebuf), "/tmp/lept/golden/%s_golden.%02d%s",
+             rp->testname, rp->index, ext);
+    LEPT_FREE(ext);
+
+        /* Generate mode.  No testing. */
+    if (rp->mode == L_REG_GENERATE) {
+            /* Save the file as a golden file */
+        ret = fileCopy(localname, namebuf);
+#if 0       /* Enable for details on writing of golden files */
+        if (!ret) {
+            char *local = genPathname(localname, NULL);
+            char *golden = genPathname(namebuf, NULL);
+            L_INFO("Copy: %s to %s\n", __func__, local, golden);
+            LEPT_FREE(local);
+            LEPT_FREE(golden);
+        }
+#endif
+        return ret;
+    }
+
+        /* Compare mode: test and record on failure.  This can be used
+         * for all image formats, as well as for all files of serialized
+         * data, such as boxa, pta, etc.  In all cases except for
+         * GIF compressed images, we compare the files to see if they
+         * are identical.  GIF doesn't support RGB images; to write
+         * a 32 bpp RGB image in GIF, we do a lossy quantization to
+         * 256 colors, so the cycle read-RGB/write-GIF is not idempotent.
+         * And although the read/write cycle for GIF images with bpp <= 8
+         * is idempotent in the image pixels, it is not idempotent in the
+         * actual file bytes; tests comparing file bytes before and after
+         * a GIF read/write cycle will fail.  So for GIF we uncompress
+         * the two images and compare the actual pixels.  PNG is both
+         * lossless and idempotent in file bytes on read/write, so it is
+         * not necessary to compare pixels.  (Comparing pixels requires
+         * decompression, and thus would increase the regression test
+         * time.  JPEG is lossy and not idempotent in the image pixels,
+         * so no tests are constructed that would require it. */
+    findFileFormat(localname, &format);
+    if (format == IFF_GIF) {
+        same = 0;
+        pix1 = pixRead(localname);
+        pix2 = pixRead(namebuf);
+        pixEqual(pix1, pix2, &same);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    } else {
+        filesAreIdentical(localname, namebuf, &same);
+    }
+    if (!same) {
+        fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n",
+                rp->testname, rp->index, localname, namebuf);
+        lept_stderr("Failure in %s_reg, index %d: comparing %s with %s\n",
+                    rp->testname, rp->index, localname, namebuf);
+        rp->success = FALSE;
+    }
+
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestCompareFiles()
+ *
+ * \param[in]    rp        regtest parameters
+ * \param[in]    index1    of one output file from reg test
+ * \param[in]    index2    of another output file from reg test
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This only does something in "compare" mode.
+ *      (2) The canonical format of the golden filenames is:
+ *            /tmp/lept/golden/[root of main name]_golden.[index].
+ *                                                      [ext of localname]
+ *          e.g.,
+ *            /tmp/lept/golden/maze_golden.0.png
+ * </pre>
+ */
+l_ok
+regTestCompareFiles(L_REGPARAMS  *rp,
+                    l_int32       index1,
+                    l_int32       index2)
+{
+char    *name1, *name2;
+char     namebuf[256];
+l_int32  same;
+SARRAY  *sa;
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+    if (index1 < 0 || index2 < 0) {
+        rp->success = FALSE;
+        return ERROR_INT("index1 and/or index2 is negative", __func__, 1);
+    }
+    if (index1 == index2) {
+        rp->success = FALSE;
+        return ERROR_INT("index1 must differ from index2", __func__, 1);
+    }
+
+    rp->index++;
+    if (rp->mode != L_REG_COMPARE) return 0;
+
+        /* Generate the golden file names */
+    snprintf(namebuf, sizeof(namebuf), "%s_golden.%02d", rp->testname, index1);
+    sa = getSortedPathnamesInDirectory("/tmp/lept/golden", namebuf, 0, 0);
+    if (sarrayGetCount(sa) != 1) {
+        sarrayDestroy(&sa);
+        rp->success = FALSE;
+        L_ERROR("golden file %s not found\n", __func__, namebuf);
+        return 1;
+    }
+    name1 = sarrayGetString(sa, 0, L_COPY);
+    sarrayDestroy(&sa);
+
+    snprintf(namebuf, sizeof(namebuf), "%s_golden.%02d", rp->testname, index2);
+    sa = getSortedPathnamesInDirectory("/tmp/lept/golden", namebuf, 0, 0);
+    if (sarrayGetCount(sa) != 1) {
+        sarrayDestroy(&sa);
+        rp->success = FALSE;
+        LEPT_FREE(name1);
+        L_ERROR("golden file %s not found\n", __func__, namebuf);
+        return 1;
+    }
+    name2 = sarrayGetString(sa, 0, L_COPY);
+    sarrayDestroy(&sa);
+
+        /* Test and record on failure */
+    filesAreIdentical(name1, name2, &same);
+    if (!same) {
+        fprintf(rp->fp,
+                "Failure in %s_reg, index %d: comparing %s with %s\n",
+                rp->testname, rp->index, name1, name2);
+        lept_stderr("Failure in %s_reg, index %d: comparing %s with %s\n",
+                    rp->testname, rp->index, name1, name2);
+        rp->success = FALSE;
+    }
+
+    LEPT_FREE(name1);
+    LEPT_FREE(name2);
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestWritePixAndCheck()
+ *
+ * \param[in]    rp       regtest parameters
+ * \param[in]    pix      to be written
+ * \param[in]    format   of output pix
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This function makes it easy to write the pix in a numbered
+ *          sequence of files, and either to:
+ *             (a) write the golden file ("generate" arg to regression test)
+ *             (b) make a local file and "compare" with the golden file
+ *             (c) make a local file and "display" the results
+ *      (2) The canonical format of the local filename is:
+ *            /tmp/lept/regout/[root of main name].[count].[format extension]
+ *          e.g., for scale_reg,
+ *            /tmp/lept/regout/scale.0.png
+ *          The golden file name mirrors this in the usual way.
+ *      (3) The check is done between the written files, which requires
+ *          the files to be identical. The exception is for GIF, which
+ *          only requires that all pixels in the decoded pix are identical.
+ * </pre>
+ */
+l_ok
+regTestWritePixAndCheck(L_REGPARAMS  *rp,
+                        PIX          *pix,
+                        l_int32       format)
+{
+char  namebuf[256];
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+    if (!pix) {
+        rp->success = FALSE;
+        return ERROR_INT("pix not defined", __func__, 1);
+    }
+    if (format < 0 || format >= NumImageFileFormatExtensions) {
+        rp->success = FALSE;
+        return ERROR_INT("invalid format", __func__, 1);
+    }
+
+        /* Use bmp format for testing if library for requested
+         * format for jpeg, png or tiff is not available */
+    changeFormatForMissingLib(&format);
+
+        /* Generate the local file name */
+    snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s",
+             rp->testname, rp->index + 1, ImageFileFormatExtensions[format]);
+
+        /* Write the local file */
+    if (pixGetDepth(pix) < 8)
+        pixSetPadBits(pix, 0);
+    pixWrite(namebuf, pix, format);
+
+        /* Either write the golden file ("generate") or check the
+           local file against an existing golden file ("compare") */
+    regTestCheckFile(rp, namebuf);
+
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestWriteDataAndCheck()
+ *
+ * \param[in]    rp      regtest parameters
+ * \param[in]    data    to be written
+ * \param[in]    nbytes  of data to be written
+ * \param[in]    ext     filename extension (e.g.: "ba", "pta")
+ * \return  0 if OK, 1 on error
+ *               Note: a failure in comparison is not an error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This function makes it easy to write data in a numbered
+ *          sequence of files, and either to:
+ *             (a) write the golden file ("generate" arg to regression test)
+ *             (b) make a local file and "compare" with the golden file
+ *             (c) make a local file and "display" the results
+ *      (2) The canonical format of the local filename is:
+ *            /tmp/lept/regout/[root of main name].[count].[ext]
+ *          e.g., for the first boxaa in quadtree_reg,
+ *            /tmp/lept/regout/quadtree.0.baa
+ *          The golden file name mirrors this in the usual way.
+ *      (3) The data can be anything.  It is most useful for serialized
+ *          output of data, such as boxa, pta, etc.
+ *      (4) The file extension is arbitrary.  It is included simply
+ *          to make the content type obvious when examining written files.
+ *      (5) The check is done between the written files, which requires
+ *          the files to be identical.
+ * </pre>
+ */
+l_ok
+regTestWriteDataAndCheck(L_REGPARAMS  *rp,
+                         void         *data,
+                         size_t        nbytes,
+                         const char   *ext)
+{
+char  namebuf[256];
+
+    if (!rp)
+        return ERROR_INT("rp not defined", __func__, 1);
+    if (!data || nbytes == 0) {
+        rp->success = FALSE;
+        return ERROR_INT("data not defined or size == 0", __func__, 1);
+    }
+
+        /* Generate the local file name */
+    snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s",
+             rp->testname, rp->index + 1, ext);
+
+        /* Write the local file */
+    l_binaryWrite(namebuf, "w", data, nbytes);
+
+        /* Either write the golden file ("generate") or check the
+           local file against an existing golden file ("compare") */
+    regTestCheckFile(rp, namebuf);
+    return 0;
+}
+
+
+/*!
+ * \brief   regTestGenLocalFilename()
+ *
+ * \param[in]       rp      regtest parameters
+ * \param[in]       index   use -1 for current index
+ * \param[in]       format  of image; e.g., IFF_PNG
+ * \return  filename if OK, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This is used to get the name of a file in the regout
+ *          subdirectory, that has been made and is used to test against
+ *          the golden file.  You can either specify a particular index
+ *          value, or with %index == -1, this returns the most recently
+ *          written file.  The latter case lets you read a pix from a
+ *          file that has just been written with regTestWritePixAndCheck(),
+ *          which is useful for testing formatted read/write functions.
+ *
+ * </pre>
+ */
+char *
+regTestGenLocalFilename(L_REGPARAMS  *rp,
+                        l_int32       index,
+                        l_int32       format)
+{
+char     buf[64];
+l_int32  ind;
+
+    if (!rp)
+        return (char *)ERROR_PTR("rp not defined", __func__, NULL);
+
+    ind = (index >= 0) ? index : rp->index;
+    snprintf(buf, sizeof(buf), "/tmp/lept/regout/%s.%02d.%s",
+             rp->testname, ind, ImageFileFormatExtensions[format]);
+    return stringNew(buf);
+}
+
+
+/*!
+ * \brief   getRootNameFromArgv0()
+ *
+ * \param[in]    argv0
+ * \return  root name without the '_reg', or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) For example, from psioseg_reg, we want to extract
+ *          just 'psioseg' as the root.
+ *      (2) In unix with autotools, the executable is not X,
+ *          but ./.libs/lt-X.   So in addition to stripping out the
+ *          last 4 characters of the tail, we have to check for
+ *          the '-' and strip out the "lt-" prefix if we find it.
+ * </pre>
+ */
+static char *
+getRootNameFromArgv0(const char  *argv0)
+{
+l_int32  len;
+char    *root;
+
+    splitPathAtDirectory(argv0, NULL, &root);
+    if ((len = strlen(root)) <= 4) {
+        LEPT_FREE(root);
+        return (char *)ERROR_PTR("invalid argv0; too small", __func__, NULL);
+    }
+
+#ifndef _WIN32
+    {
+        char    *newroot;
+        l_int32  loc;
+        if (stringFindSubstr(root, "-", &loc)) {
+            newroot = stringNew(root + loc + 1);  /* strip out "lt-" */
+            LEPT_FREE(root);
+            root = newroot;
+            len = strlen(root);
+        }
+        len -= 4;  /* remove the "_reg" suffix */
+    }
+#else
+    if (strstr(root, ".exe") != NULL)
+        len -= 4;
+    if (strstr(root, "_reg") == root + len - 4)
+        len -= 4;
+#endif  /* ! _WIN32 */
+
+    root[len] = '\0';  /* terminate */
+    return root;
+}