diff options
Diffstat (limited to 'leptonica/src/sel1.c')
-rw-r--r-- | leptonica/src/sel1.c | 2446 |
1 files changed, 2446 insertions, 0 deletions
diff --git a/leptonica/src/sel1.c b/leptonica/src/sel1.c new file mode 100644 index 00000000..492972cd --- /dev/null +++ b/leptonica/src/sel1.c @@ -0,0 +1,2446 @@ +/*====================================================================* + - 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 sel1.c + * <pre> + * + * Basic ops on Sels and Selas + * + * Create/destroy/copy: + * SELA *selaCreate() + * void selaDestroy() + * SEL *selCreate() + * void selDestroy() + * SEL *selCopy() + * SEL *selCreateBrick() + * SEL *selCreateComb() + * + * Helper proc: + * l_int32 **create2dIntArray() + * + * Extension of sela: + * SELA *selaAddSel() + * static l_int32 selaExtendArray() + * + * Accessors: + * l_int32 selaGetCount() + * SEL *selaGetSel() + * char *selGetName() + * l_int32 selSetName() + * l_int32 selaFindSelByName() + * l_int32 selGetElement() + * l_int32 selSetElement() + * l_int32 selGetParameters() + * l_int32 selSetOrigin() + * l_int32 selGetTypeAtOrigin() + * char *selaGetBrickName() + * char *selaGetCombName() + * static char *selaComputeCompositeParameters() + * l_int32 getCompositeParameters() + * SARRAY *selaGetSelnames() + * + * Max translations for erosion and hmt + * l_int32 selFindMaxTranslations() + * + * Rotation by multiples of 90 degrees + * SEL *selRotateOrth() + * + * Sela and Sel serialized I/O + * SELA *selaRead() + * SELA *selaReadStream() + * SEL *selRead() + * SEL *selReadStream() + * l_int32 selaWrite() + * l_int32 selaWriteStream() + * l_int32 selWrite() + * l_int32 selWriteStream() + * + * Building custom hit-miss sels from compiled strings + * SEL *selCreateFromString() + * char *selPrintToString() [for debugging] + * + * Building custom hit-miss sels from a simple file format + * SELA *selaCreateFromFile() + * static SEL *selCreateFromSArray() + * + * Making hit-only sels from Pta and Pix + * SEL *selCreateFromPta() + * SEL *selCreateFromPix() + * + * Making hit-miss sels from Pix and image files + * SEL *selReadFromColorImage() + * SEL *selCreateFromColorPix() + SELA *selaCreateFromColorPixa() + * + * Printable display of sel + * PIX *selDisplayInPix() + * PIX *selaDisplayInPix() + * + * Usage notes: + * In this file we have seven functions that make sels: + * (1) selCreate(), with input (h, w, [name]) + * The generic function. Roll your own, using selSetElement(). + * (2) selCreateBrick(), with input (h, w, cy, cx, val) + * The most popular function. Makes a rectangular sel of + * all hits, misses or don't-cares. We have many morphology + * operations that create a sel of all hits, use it, and + * destroy it. + * (3) selCreateFromString() with input (text, h, w, [name]) + * Adam Langley's clever function, allows you to make a hit-miss + * sel from a string in code that is geometrically laid out + * just like the actual sel. + * (4) selaCreateFromFile() with input (filename) + * This parses a simple file format to create an array of + * hit-miss sels. The sel data uses the same encoding + * as in (3), with geometrical layout enforced. + * (5) selCreateFromPta() with input (pta, cy, cx, [name]) + * Another way to make a sel with only hits. + * (6) selCreateFromPix() with input (pix, cy, cx, [name]) + * Yet another way to make a sel from hits. + * (7) selCreateFromColorPix() with input (pix, name). + * Another way to make a general hit-miss sel, starting with + * an image editor. + * In addition, there are three functions in selgen.c that + * automatically generate a hit-miss sel from a pix and + * a number of parameters. This is useful for problems like + * "find all patterns that look like this one." + * + * Consistency, being the hobgoblin of small minds, + * is adhered to here in the dimensioning and accessing of sels. + * Everything is done in standard matrix (row, column) order. + * When we set specific elements in a sel, we likewise use + * (row, col) ordering: + * selSetElement(), with input (row, col, type) + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include "allheaders.h" + + /* Bounds on sel ptr array size */ +static const l_uint32 MaxPtrArraySize = 10000; +static const l_int32 InitialPtrArraySize = 50; /*!< n'importe quoi */ + + /* Bounds on kernel size */ +static const l_uint32 MaxKernelSize = 10000; + + /* Bounds on pix template size */ +static const l_uint32 MaxPixTemplateSize = 100; +static const l_uint32 MaxPixTemplateHits = 1000; + + /* Static functions */ +static l_int32 selaExtendArray(SELA *sela); +static SEL *selCreateFromSArray(SARRAY *sa, l_int32 first, l_int32 last); + +struct CompParameterMap +{ + l_int32 size; + l_int32 size1; + l_int32 size2; + char selnameh1[20]; + char selnameh2[20]; + char selnamev1[20]; + char selnamev2[20]; +}; + +static const struct CompParameterMap comp_parameter_map[] = + { { 2, 2, 1, "sel_2h", "", "sel_2v", "" }, + { 3, 3, 1, "sel_3h", "", "sel_3v", "" }, + { 4, 2, 2, "sel_2h", "sel_comb_4h", "sel_2v", "sel_comb_4v" }, + { 5, 5, 1, "sel_5h", "", "sel_5v", "" }, + { 6, 3, 2, "sel_3h", "sel_comb_6h", "sel_3v", "sel_comb_6v" }, + { 7, 7, 1, "sel_7h", "", "sel_7v", "" }, + { 8, 4, 2, "sel_4h", "sel_comb_8h", "sel_4v", "sel_comb_8v" }, + { 9, 3, 3, "sel_3h", "sel_comb_9h", "sel_3v", "sel_comb_9v" }, + { 10, 5, 2, "sel_5h", "sel_comb_10h", "sel_5v", "sel_comb_10v" }, + { 11, 4, 3, "sel_4h", "sel_comb_12h", "sel_4v", "sel_comb_12v" }, + { 12, 4, 3, "sel_4h", "sel_comb_12h", "sel_4v", "sel_comb_12v" }, + { 13, 4, 3, "sel_4h", "sel_comb_12h", "sel_4v", "sel_comb_12v" }, + { 14, 7, 2, "sel_7h", "sel_comb_14h", "sel_7v", "sel_comb_14v" }, + { 15, 5, 3, "sel_5h", "sel_comb_15h", "sel_5v", "sel_comb_15v" }, + { 16, 4, 4, "sel_4h", "sel_comb_16h", "sel_4v", "sel_comb_16v" }, + { 17, 4, 4, "sel_4h", "sel_comb_16h", "sel_4v", "sel_comb_16v" }, + { 18, 6, 3, "sel_6h", "sel_comb_18h", "sel_6v", "sel_comb_18v" }, + { 19, 5, 4, "sel_5h", "sel_comb_20h", "sel_5v", "sel_comb_20v" }, + { 20, 5, 4, "sel_5h", "sel_comb_20h", "sel_5v", "sel_comb_20v" }, + { 21, 7, 3, "sel_7h", "sel_comb_21h", "sel_7v", "sel_comb_21v" }, + { 22, 11, 2, "sel_11h", "sel_comb_22h", "sel_11v", "sel_comb_22v" }, + { 23, 6, 4, "sel_6h", "sel_comb_24h", "sel_6v", "sel_comb_24v" }, + { 24, 6, 4, "sel_6h", "sel_comb_24h", "sel_6v", "sel_comb_24v" }, + { 25, 5, 5, "sel_5h", "sel_comb_25h", "sel_5v", "sel_comb_25v" }, + { 26, 5, 5, "sel_5h", "sel_comb_25h", "sel_5v", "sel_comb_25v" }, + { 27, 9, 3, "sel_9h", "sel_comb_27h", "sel_9v", "sel_comb_27v" }, + { 28, 7, 4, "sel_7h", "sel_comb_28h", "sel_7v", "sel_comb_28v" }, + { 29, 6, 5, "sel_6h", "sel_comb_30h", "sel_6v", "sel_comb_30v" }, + { 30, 6, 5, "sel_6h", "sel_comb_30h", "sel_6v", "sel_comb_30v" }, + { 31, 6, 5, "sel_6h", "sel_comb_30h", "sel_6v", "sel_comb_30v" }, + { 32, 8, 4, "sel_8h", "sel_comb_32h", "sel_8v", "sel_comb_32v" }, + { 33, 11, 3, "sel_11h", "sel_comb_33h", "sel_11v", "sel_comb_33v" }, + { 34, 7, 5, "sel_7h", "sel_comb_35h", "sel_7v", "sel_comb_35v" }, + { 35, 7, 5, "sel_7h", "sel_comb_35h", "sel_7v", "sel_comb_35v" }, + { 36, 6, 6, "sel_6h", "sel_comb_36h", "sel_6v", "sel_comb_36v" }, + { 37, 6, 6, "sel_6h", "sel_comb_36h", "sel_6v", "sel_comb_36v" }, + { 38, 6, 6, "sel_6h", "sel_comb_36h", "sel_6v", "sel_comb_36v" }, + { 39, 13, 3, "sel_13h", "sel_comb_39h", "sel_13v", "sel_comb_39v" }, + { 40, 8, 5, "sel_8h", "sel_comb_40h", "sel_8v", "sel_comb_40v" }, + { 41, 7, 6, "sel_7h", "sel_comb_42h", "sel_7v", "sel_comb_42v" }, + { 42, 7, 6, "sel_7h", "sel_comb_42h", "sel_7v", "sel_comb_42v" }, + { 43, 7, 6, "sel_7h", "sel_comb_42h", "sel_7v", "sel_comb_42v" }, + { 44, 11, 4, "sel_11h", "sel_comb_44h", "sel_11v", "sel_comb_44v" }, + { 45, 9, 5, "sel_9h", "sel_comb_45h", "sel_9v", "sel_comb_45v" }, + { 46, 9, 5, "sel_9h", "sel_comb_45h", "sel_9v", "sel_comb_45v" }, + { 47, 8, 6, "sel_8h", "sel_comb_48h", "sel_8v", "sel_comb_48v" }, + { 48, 8, 6, "sel_8h", "sel_comb_48h", "sel_8v", "sel_comb_48v" }, + { 49, 7, 7, "sel_7h", "sel_comb_49h", "sel_7v", "sel_comb_49v" }, + { 50, 10, 5, "sel_10h", "sel_comb_50h", "sel_10v", "sel_comb_50v" }, + { 51, 10, 5, "sel_10h", "sel_comb_50h", "sel_10v", "sel_comb_50v" }, + { 52, 13, 4, "sel_13h", "sel_comb_52h", "sel_13v", "sel_comb_52v" }, + { 53, 9, 6, "sel_9h", "sel_comb_54h", "sel_9v", "sel_comb_54v" }, + { 54, 9, 6, "sel_9h", "sel_comb_54h", "sel_9v", "sel_comb_54v" }, + { 55, 11, 5, "sel_11h", "sel_comb_55h", "sel_11v", "sel_comb_55v" }, + { 56, 8, 7, "sel_8h", "sel_comb_56h", "sel_8v", "sel_comb_56v" }, + { 57, 8, 7, "sel_8h", "sel_comb_56h", "sel_8v", "sel_comb_56v" }, + { 58, 8, 7, "sel_8h", "sel_comb_56h", "sel_8v", "sel_comb_56v" }, + { 59, 10, 6, "sel_10h", "sel_comb_60h", "sel_10v", "sel_comb_60v" }, + { 60, 10, 6, "sel_10h", "sel_comb_60h", "sel_10v", "sel_comb_60v" }, + { 61, 10, 6, "sel_10h", "sel_comb_60h", "sel_10v", "sel_comb_60v" }, + { 62, 9, 7, "sel_9h", "sel_comb_63h", "sel_9v", "sel_comb_63v" }, + { 63, 9, 7, "sel_9h", "sel_comb_63h", "sel_9v", "sel_comb_63v" } }; + + + +/*------------------------------------------------------------------------* + * Create / Destroy / Copy * + *------------------------------------------------------------------------*/ +/*! + * \brief selaCreate() + * + * \param[in] n initial number of sel ptrs; use 0 for default + * \return sela, or NULL on error + */ +SELA * +selaCreate(l_int32 n) +{ +SELA *sela; + + PROCNAME("selaCreate"); + + if (n <= 0 || n > MaxPtrArraySize) + n = InitialPtrArraySize; + + /* Make array of sel ptrs */ + sela = (SELA *)LEPT_CALLOC(1, sizeof(SELA)); + sela->nalloc = n; + sela->n = 0; + if ((sela->sel = (SEL **)LEPT_CALLOC(n, sizeof(SEL *))) == NULL) { + LEPT_FREE(sela); + return (SELA *)ERROR_PTR("sel ptrs not made", procName, NULL); + } + return sela; +} + + +/*! + * \brief selaDestroy() + * + * \param[in,out] psela will be set to null before returning + * \return void + */ +void +selaDestroy(SELA **psela) +{ +SELA *sela; +l_int32 i; + + if (!psela) return; + if ((sela = *psela) == NULL) + return; + + for (i = 0; i < sela->n; i++) + selDestroy(&sela->sel[i]); + LEPT_FREE(sela->sel); + LEPT_FREE(sela); + *psela = NULL; +} + + +/*! + * \brief selCreate() + * + * \param[in] height + * \param[in] width + * \param[in] name [optional] sel name; can be null + * \return sel, or NULL on error + * + * <pre> + * Notes: + * (1) selCreate() initializes all values to 0. + * (2) After this call, (cy,cx) and nonzero data values must be + * assigned. If a text name is not assigned here, it will + * be needed later when the sel is put into a sela. + * </pre> + */ +SEL * +selCreate(l_int32 height, + l_int32 width, + const char *name) +{ +SEL *sel; + + PROCNAME("selCreate"); + + if ((sel = (SEL *)LEPT_CALLOC(1, sizeof(SEL))) == NULL) + return (SEL *)ERROR_PTR("sel not made", procName, NULL); + if (name) + sel->name = stringNew(name); + sel->sy = height; + sel->sx = width; + if ((sel->data = create2dIntArray(height, width)) == NULL) { + LEPT_FREE(sel->name); + LEPT_FREE(sel); + return (SEL *)ERROR_PTR("data not allocated", procName, NULL); + } + + return sel; +} + + +/*! + * \brief selDestroy() + * + * \param[in,out] psel will be set to null before returning + * \return void + */ +void +selDestroy(SEL **psel) +{ +l_int32 i; +SEL *sel; + + PROCNAME("selDestroy"); + + if (psel == NULL) { + L_WARNING("ptr address is NULL!\n", procName); + return; + } + if ((sel = *psel) == NULL) + return; + + for (i = 0; i < sel->sy; i++) + LEPT_FREE(sel->data[i]); + LEPT_FREE(sel->data); + if (sel->name) + LEPT_FREE(sel->name); + LEPT_FREE(sel); + *psel = NULL; +} + + +/*! + * \brief selCopy() + * + * \param[in] sel + * \return a copy of the sel, or NULL on error + */ +SEL * +selCopy(SEL *sel) +{ +l_int32 sx, sy, cx, cy, i, j; +SEL *csel; + + PROCNAME("selCopy"); + + if (!sel) + return (SEL *)ERROR_PTR("sel not defined", procName, NULL); + + if ((csel = (SEL *)LEPT_CALLOC(1, sizeof(SEL))) == NULL) + return (SEL *)ERROR_PTR("csel not made", procName, NULL); + selGetParameters(sel, &sy, &sx, &cy, &cx); + csel->sy = sy; + csel->sx = sx; + csel->cy = cy; + csel->cx = cx; + + if ((csel->data = create2dIntArray(sy, sx)) == NULL) { + LEPT_FREE(csel); + return (SEL *)ERROR_PTR("sel data not made", procName, NULL); + } + + for (i = 0; i < sy; i++) + for (j = 0; j < sx; j++) + csel->data[i][j] = sel->data[i][j]; + + if (sel->name) + csel->name = stringNew(sel->name); + + return csel; +} + + +/*! + * \brief selCreateBrick() + * + * \param[in] h, w height, width + * \param[in] cy, cx origin, relative to UL corner at 0,0 + * \param[in] type SEL_HIT, SEL_MISS, or SEL_DONT_CARE + * \return sel, or NULL on error + * + * <pre> + * Notes: + * (1) This is a rectangular sel of all hits, misses or don't cares. + * </pre> + */ +SEL * +selCreateBrick(l_int32 h, + l_int32 w, + l_int32 cy, + l_int32 cx, + l_int32 type) +{ +l_int32 i, j; +SEL *sel; + + PROCNAME("selCreateBrick"); + + if (h <= 0 || w <= 0) + return (SEL *)ERROR_PTR("h and w must both be > 0", procName, NULL); + if (type != SEL_HIT && type != SEL_MISS && type != SEL_DONT_CARE) + return (SEL *)ERROR_PTR("invalid sel element type", procName, NULL); + + if ((sel = selCreate(h, w, NULL)) == NULL) + return (SEL *)ERROR_PTR("sel not made", procName, NULL); + selSetOrigin(sel, cy, cx); + for (i = 0; i < h; i++) + for (j = 0; j < w; j++) + sel->data[i][j] = type; + + return sel; +} + + +/*! + * \brief selCreateComb() + * + * \param[in] factor1 contiguous space between comb tines + * \param[in] factor2 number of comb tines + * \param[in] direction L_HORIZ, L_VERT + * \return sel, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a comb Sel of hits with the origin as + * near the center as possible. + * (2) In use, this is complemented by a brick sel of size %factor1, + * Both brick and comb sels are made by selectComposableSels(). + * </pre> + */ +SEL * +selCreateComb(l_int32 factor1, + l_int32 factor2, + l_int32 direction) +{ +l_int32 i, size, z; +SEL *sel; + + PROCNAME("selCreateComb"); + + if (factor1 < 1 || factor2 < 1) + return (SEL *)ERROR_PTR("factors must be >= 1", procName, NULL); + if (direction != L_HORIZ && direction != L_VERT) + return (SEL *)ERROR_PTR("invalid direction", procName, NULL); + + size = factor1 * factor2; + if (direction == L_HORIZ) { + if ((sel = selCreate(1, size, NULL)) == NULL) + return (SEL *)ERROR_PTR("horiz sel not made", procName, NULL); + selSetOrigin(sel, 0, size / 2); + } else { + if ((sel = selCreate(size, 1, NULL)) == NULL) + return (SEL *)ERROR_PTR("vert sel not made", procName, NULL); + selSetOrigin(sel, size / 2, 0); + } + + /* Lay down the elements of the comb */ + for (i = 0; i < factor2; i++) { + z = factor1 / 2 + i * factor1; +/* lept_stderr("i = %d, factor1 = %d, factor2 = %d, z = %d\n", + i, factor1, factor2, z); */ + if (direction == L_HORIZ) + selSetElement(sel, 0, z, SEL_HIT); + else + selSetElement(sel, z, 0, SEL_HIT); + } + + return sel; +} + + +/*! + * \brief create2dIntArray() + * + * \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 ints + * + * <pre> + * Notes: + * (1) The array[sy][sx] is indexed in standard "matrix notation", + * with the row index first. + * </pre> + */ +l_int32 ** +create2dIntArray(l_int32 sy, + l_int32 sx) +{ +l_int32 i, j, success; +l_int32 **array; + + PROCNAME("create2dIntArray"); + + if (sx <= 0 || sx > MaxKernelSize) + return (l_int32 **)ERROR_PTR("sx out of bounds", procName, NULL); + if (sy <= 0 || sy > MaxKernelSize) + return (l_int32 **)ERROR_PTR("sy out of bounds", procName, NULL); + + if ((array = (l_int32 **)LEPT_CALLOC(sy, sizeof(l_int32 *))) == NULL) + return (l_int32 **)ERROR_PTR("ptr array not made", procName, NULL); + success = TRUE; + for (i = 0; i < sy; i++) { + if ((array[i] = (l_int32 *)LEPT_CALLOC(sx, sizeof(l_int32))) == NULL) { + success = FALSE; + break; + } + } + if (success) return array; + + /* Cleanup after error */ + for (j = 0; j < i; j++) + LEPT_FREE(array[j]); + LEPT_FREE(array); + return (l_int32 **)ERROR_PTR("array not made", procName, NULL); +} + + + +/*------------------------------------------------------------------------* + * Extension of sela * + *------------------------------------------------------------------------*/ +/*! + * \brief selaAddSel() + * + * \param[in] sela + * \param[in] sel to be added + * \param[in] selname ignored if already defined in sel; + * req'd in sel when added to a sela + * \param[in] copyflag L_INSERT or L_COPY + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This adds a sel, either inserting or making a copy. + * (2) Because every sel in a sela must have a name, it copies + * the input name if necessary. You can input NULL for + * selname if the sel already has a name. + * </pre> + */ +l_ok +selaAddSel(SELA *sela, + SEL *sel, + const char *selname, + l_int32 copyflag) +{ +l_int32 n; +SEL *csel; + + PROCNAME("selaAddSel"); + + if (!sela) + return ERROR_INT("sela not defined", procName, 1); + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + if (!sel->name && !selname) + return ERROR_INT("added sel must have name", procName, 1); + if (copyflag != L_INSERT && copyflag != L_COPY) + return ERROR_INT("invalid copyflag", procName, 1); + + if (copyflag == L_COPY) { + if ((csel = selCopy(sel)) == NULL) + return ERROR_INT("csel not made", procName, 1); + } else { /* copyflag == L_INSERT */ + csel = sel; + } + if (!csel->name) + csel->name = stringNew(selname); + + n = selaGetCount(sela); + if (n >= sela->nalloc) { + if (selaExtendArray(sela)) { + if (copyflag != L_INSERT) + selDestroy(&csel); + return ERROR_INT("extension failed", procName, 1); + } + } + + sela->sel[n] = csel; + sela->n++; + return 0; +} + + +/*! + * \brief selaExtendArray() + * + * \param[in] sela + * \return 0 if OK; 1 on error + */ +static l_int32 +selaExtendArray(SELA *sela) +{ + PROCNAME("selaExtendArray"); + + if (!sela) + return ERROR_INT("sela not defined", procName, 1); + + if ((sela->sel = (SEL **)reallocNew((void **)&sela->sel, + sizeof(SEL *) * sela->nalloc, + 2 * sizeof(SEL *) * sela->nalloc)) == NULL) + return ERROR_INT("new ptr array not returned", procName, 1); + + sela->nalloc = 2 * sela->nalloc; + return 0; +} + + + +/*----------------------------------------------------------------------* + * Accessors * + *----------------------------------------------------------------------*/ +/*! + * \brief selaGetCount() + * + * \param[in] sela + * \return count, or 0 on error + */ +l_int32 +selaGetCount(SELA *sela) +{ + PROCNAME("selaGetCount"); + + if (!sela) + return ERROR_INT("sela not defined", procName, 0); + + return sela->n; +} + + +/*! + * \brief selaGetSel() + * + * \param[in] sela + * \param[in] i index of sel to be retrieved not copied + * \return sel, or NULL on error + * + * <pre> + * Notes: + * (1) This returns a ptr to the sel, not a copy, so the caller + * must not destroy it! + * </pre> + */ +SEL * +selaGetSel(SELA *sela, + l_int32 i) +{ + PROCNAME("selaGetSel"); + + if (!sela) + return (SEL *)ERROR_PTR("sela not defined", procName, NULL); + + if (i < 0 || i >= sela->n) + return (SEL *)ERROR_PTR("invalid index", procName, NULL); + return sela->sel[i]; +} + + +/*! + * \brief selGetName() + * + * \param[in] sel + * \return sel name not copied, or NULL if no name or on error + */ +char * +selGetName(SEL *sel) +{ + PROCNAME("selGetName"); + + if (!sel) + return (char *)ERROR_PTR("sel not defined", procName, NULL); + + return sel->name; +} + + +/*! + * \brief selSetName() + * + * \param[in] sel + * \param[in] name [optional]; can be null + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Always frees the existing sel name, if defined. + * (2) If name is not defined, just clears any existing sel name. + * </pre> + */ +l_ok +selSetName(SEL *sel, + const char *name) +{ + PROCNAME("selSetName"); + + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + + return stringReplace(&sel->name, name); +} + + +/*! + * \brief selaFindSelByName() + * + * \param[in] sela + * \param[in] name sel name + * \param[out] pindex [optional] + * \param[in] psel [optional] sel (not a copy) + * \return 0 if OK; 1 on error + */ +l_ok +selaFindSelByName(SELA *sela, + const char *name, + l_int32 *pindex, + SEL **psel) +{ +l_int32 i, n; +char *sname; +SEL *sel; + + PROCNAME("selaFindSelByName"); + + if (pindex) *pindex = -1; + if (psel) *psel = NULL; + + if (!sela) + return ERROR_INT("sela not defined", procName, 1); + + n = selaGetCount(sela); + for (i = 0; i < n; i++) + { + if ((sel = selaGetSel(sela, i)) == NULL) { + L_WARNING("missing sel\n", procName); + continue; + } + + sname = selGetName(sel); + if (sname && (!strcmp(name, sname))) { + if (pindex) + *pindex = i; + if (psel) + *psel = sel; + return 0; + } + } + + return 1; +} + + +/*! + * \brief selGetElement() + * + * \param[in] sel + * \param[in] row + * \param[in] col + * \param[out] ptype SEL_HIT, SEL_MISS, SEL_DONT_CARE + * \return 0 if OK; 1 on error + */ +l_ok +selGetElement(SEL *sel, + l_int32 row, + l_int32 col, + l_int32 *ptype) +{ + PROCNAME("selGetElement"); + + if (!ptype) + return ERROR_INT("&type not defined", procName, 1); + *ptype = SEL_DONT_CARE; + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + if (row < 0 || row >= sel->sy) + return ERROR_INT("sel row out of bounds", procName, 1); + if (col < 0 || col >= sel->sx) + return ERROR_INT("sel col out of bounds", procName, 1); + + *ptype = sel->data[row][col]; + return 0; +} + + +/*! + * \brief selSetElement() + * + * \param[in] sel + * \param[in] row + * \param[in] col + * \param[in] type SEL_HIT, SEL_MISS, SEL_DONT_CARE + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Because we use row and column to index into an array, + * they are always non-negative. The location of the origin + * (and the type of operation) determine the actual + * direction of the rasterop. + * </pre> + */ +l_ok +selSetElement(SEL *sel, + l_int32 row, + l_int32 col, + l_int32 type) +{ + PROCNAME("selSetElement"); + + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + if (type != SEL_HIT && type != SEL_MISS && type != SEL_DONT_CARE) + return ERROR_INT("invalid sel element type", procName, 1); + if (row < 0 || row >= sel->sy) + return ERROR_INT("sel row out of bounds", procName, 1); + if (col < 0 || col >= sel->sx) + return ERROR_INT("sel col out of bounds", procName, 1); + + sel->data[row][col] = type; + return 0; +} + + +/*! + * \brief selGetParameters() + * + * \param[in] sel + * \param[out] psy, psx, pcy, pcx [optional] each can be null + * \return 0 if OK, 1 on error + */ +l_ok +selGetParameters(SEL *sel, + l_int32 *psy, + l_int32 *psx, + l_int32 *pcy, + l_int32 *pcx) +{ + PROCNAME("selGetParameters"); + + if (psy) *psy = 0; + if (psx) *psx = 0; + if (pcy) *pcy = 0; + if (pcx) *pcx = 0; + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + if (psy) *psy = sel->sy; + if (psx) *psx = sel->sx; + if (pcy) *pcy = sel->cy; + if (pcx) *pcx = sel->cx; + return 0; +} + + +/*! + * \brief selSetOrigin() + * + * \param[in] sel + * \param[in] cy, cx + * \return 0 if OK; 1 on error + */ +l_ok +selSetOrigin(SEL *sel, + l_int32 cy, + l_int32 cx) +{ + PROCNAME("selSetOrigin"); + + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + sel->cy = cy; + sel->cx = cx; + return 0; +} + + +/*! + * \brief selGetTypeAtOrigin() + * + * \param[in] sel + * \param[out] ptype SEL_HIT, SEL_MISS, SEL_DONT_CARE + * \return 0 if OK; 1 on error or if origin is not found + */ +l_ok +selGetTypeAtOrigin(SEL *sel, + l_int32 *ptype) +{ +l_int32 sx, sy, cx, cy, i, j; + + PROCNAME("selGetTypeAtOrigin"); + + if (!ptype) + return ERROR_INT("&type not defined", procName, 1); + *ptype = SEL_DONT_CARE; /* init */ + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + + selGetParameters(sel, &sy, &sx, &cy, &cx); + for (i = 0; i < sy; i++) { + for (j = 0; j < sx; j++) { + if (i == cy && j == cx) { + selGetElement(sel, i, j, ptype); + return 0; + } + } + } + + return ERROR_INT("sel origin not found", procName, 1); +} + + +/*! + * \brief selaGetBrickName() + * + * \param[in] sela + * \param[in] hsize, vsize of brick sel + * \return sel name new string, or NULL if no name or on error + */ +char * +selaGetBrickName(SELA *sela, + l_int32 hsize, + l_int32 vsize) +{ +l_int32 i, nsels, sx, sy; +SEL *sel; + + PROCNAME("selaGetBrickName"); + + if (!sela) + return (char *)ERROR_PTR("sela not defined", procName, NULL); + + nsels = selaGetCount(sela); + for (i = 0; i < nsels; i++) { + sel = selaGetSel(sela, i); + selGetParameters(sel, &sy, &sx, NULL, NULL); + if (hsize == sx && vsize == sy) + return stringNew(selGetName(sel)); + } + + return (char *)ERROR_PTR("sel not found", procName, NULL); +} + + +/*! + * \brief selaGetCombName() + * + * \param[in] sela + * \param[in] size the product of sizes of the brick and comb parts + * \param[in] direction L_HORIZ, L_VERT + * \return sel name new string, or NULL if name not found or on error + * + * <pre> + * Notes: + * (1) Combs are by definition 1-dimensional, either horiz or vert. + * (2) Use this with comb Sels; e.g., from selaAddDwaCombs(). + * </pre> + */ +char * +selaGetCombName(SELA *sela, + l_int32 size, + l_int32 direction) +{ +char *selname; +char combname[256]; +l_int32 i, nsels, sx, sy, found; +SEL *sel; + + PROCNAME("selaGetCombName"); + + if (!sela) + return (char *)ERROR_PTR("sela not defined", procName, NULL); + if (direction != L_HORIZ && direction != L_VERT) + return (char *)ERROR_PTR("invalid direction", procName, NULL); + + /* Derive the comb name we're looking for */ + if (direction == L_HORIZ) + snprintf(combname, sizeof(combname), "sel_comb_%dh", size); + else /* direction == L_VERT */ + snprintf(combname, sizeof(combname), "sel_comb_%dv", size); + + found = FALSE; + nsels = selaGetCount(sela); + for (i = 0; i < nsels; i++) { + sel = selaGetSel(sela, i); + selGetParameters(sel, &sy, &sx, NULL, NULL); + if (sy != 1 && sx != 1) /* 2-D; not a comb */ + continue; + selname = selGetName(sel); + if (!strcmp(selname, combname)) { + found = TRUE; + break; + } + } + + if (found) + return stringNew(selname); + else + return (char *)ERROR_PTR("sel not found", procName, NULL); +} + + +/* --------- Function used to generate code in this file ---------- */ +#if 0 +static void selaComputeCompositeParameters(const char *fileout); + +/*! + * \brief selaComputeCompParameters() + * + * \param[in] fileout + * \return void + * + * <pre> + * Notes: + * (1) This static function was used to construct the comp_parameter_map[] + * array at the top of this file. It is static because it does + * not need to be called again. It remains here to show how + * the composite parameter map was computed. + * (2) The output file was pasted directly into comp_parameter_map[]. + * The composite parameter map is used to quickly determine + * the linear decomposition parameters and sel names. + * </pre> + */ +static void +selaComputeCompositeParameters(const char *fileout) +{ +char *str, *nameh1, *nameh2, *namev1, *namev2; +char buf[256]; +l_int32 size, size1, size2, len; +SARRAY *sa; +SELA *selabasic, *selacomb; + + selabasic = selaAddBasic(NULL); + selacomb = selaAddDwaCombs(NULL); + sa = sarrayCreate(64); + for (size = 2; size < 64; size++) { + selectComposableSizes(size, &size1, &size2); + nameh1 = selaGetBrickName(selabasic, size1, 1); + namev1 = selaGetBrickName(selabasic, 1, size1); + if (size2 > 1) { + nameh2 = selaGetCombName(selacomb, size1 * size2, L_HORIZ); + namev2 = selaGetCombName(selacomb, size1 * size2, L_VERT); + } else { + nameh2 = stringNew(""); + namev2 = stringNew(""); + } + snprintf(buf, sizeof(buf), + " { %d, %d, %d, \"%s\", \"%s\", \"%s\", \"%s\" },", + size, size1, size2, nameh1, nameh2, namev1, namev2); + sarrayAddString(sa, buf, L_COPY); + LEPT_FREE(nameh1); + LEPT_FREE(nameh2); + LEPT_FREE(namev1); + LEPT_FREE(namev2); + } + str = sarrayToString(sa, 1); + len = strlen(str); + l_binaryWrite(fileout, "w", str, len + 1); + LEPT_FREE(str); + sarrayDestroy(&sa); + selaDestroy(&selabasic); + selaDestroy(&selacomb); +} +#endif +/* -------------------------------------------------------------------- */ + + +/*! + * \brief getCompositeParameters() + * + * \param[in] size + * \param[out] psize1 [optional] brick factor size + * \param[out] psize2 [optional] comb factor size + * \param[out] pnameh1 [optional] name of horiz brick + * \param[out] pnameh2 [optional] name of horiz comb + * \param[out] pnamev1 [optional] name of vert brick + * \param[out] pnamev2 [optional] name of vert comb + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This uses the big lookup table at the top of this file. + * (2) All returned strings are copies that must be freed. + * </pre> + */ +l_ok +getCompositeParameters(l_int32 size, + l_int32 *psize1, + l_int32 *psize2, + char **pnameh1, + char **pnameh2, + char **pnamev1, + char **pnamev2) +{ +l_int32 index; + + PROCNAME("selaGetSelnames"); + + if (psize1) *psize1 = 0; + if (psize2) *psize2 = 0; + if (pnameh1) *pnameh1 = NULL; + if (pnameh2) *pnameh2 = NULL; + if (pnamev1) *pnamev1 = NULL; + if (pnamev2) *pnamev2 = NULL; + if (size < 2 || size > 63) + return ERROR_INT("valid size range is {2 ... 63}", procName, 1); + index = size - 2; + if (psize1) + *psize1 = comp_parameter_map[index].size1; + if (psize2) + *psize2 = comp_parameter_map[index].size2; + if (pnameh1) + *pnameh1 = stringNew(comp_parameter_map[index].selnameh1); + if (pnameh2) + *pnameh2 = stringNew(comp_parameter_map[index].selnameh2); + if (pnamev1) + *pnamev1 = stringNew(comp_parameter_map[index].selnamev1); + if (pnamev2) + *pnamev2 = stringNew(comp_parameter_map[index].selnamev2); + return 0; +} + + +/*! + * \brief selaGetSelnames() + * + * \param[in] sela + * \return sa of all sel names, or NULL on error + */ +SARRAY * +selaGetSelnames(SELA *sela) +{ +char *selname; +l_int32 i, n; +SEL *sel; +SARRAY *sa; + + PROCNAME("selaGetSelnames"); + + if (!sela) + return (SARRAY *)ERROR_PTR("sela not defined", procName, NULL); + if ((n = selaGetCount(sela)) == 0) + return (SARRAY *)ERROR_PTR("no sels in sela", procName, NULL); + + if ((sa = sarrayCreate(n)) == NULL) + return (SARRAY *)ERROR_PTR("sa not made", procName, NULL); + for (i = 0; i < n; i++) { + sel = selaGetSel(sela, i); + selname = selGetName(sel); + sarrayAddString(sa, selname, L_COPY); + } + + return sa; +} + + + +/*----------------------------------------------------------------------* + * Max translations for erosion and hmt * + *----------------------------------------------------------------------*/ +/*! + * \brief selFindMaxTranslations() + * + * \param[in] sel + * \param[out] pxp, pyp, pxn, pyn max shifts + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + These are the maximum shifts for the erosion operation. + * For example, when j < cx, the shift of the image + * is +x to the cx. This is a positive xp shift. + * </pre> + */ +l_ok +selFindMaxTranslations(SEL *sel, + l_int32 *pxp, + l_int32 *pyp, + l_int32 *pxn, + l_int32 *pyn) +{ +l_int32 sx, sy, cx, cy, i, j; +l_int32 maxxp, maxyp, maxxn, maxyn; + + PROCNAME("selaFindMaxTranslations"); + + if (!pxp || !pyp || !pxn || !pyn) + return ERROR_INT("&xp (etc) defined", procName, 1); + *pxp = *pyp = *pxn = *pyn = 0; + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + selGetParameters(sel, &sy, &sx, &cy, &cx); + + maxxp = maxyp = maxxn = maxyn = 0; + for (i = 0; i < sy; i++) { + for (j = 0; j < sx; j++) { + if (sel->data[i][j] == 1) { + maxxp = L_MAX(maxxp, cx - j); + maxyp = L_MAX(maxyp, cy - i); + maxxn = L_MAX(maxxn, j - cx); + maxyn = L_MAX(maxyn, i - cy); + } + } + } + + *pxp = maxxp; + *pyp = maxyp; + *pxn = maxxn; + *pyn = maxyn; + + return 0; +} + + +/*----------------------------------------------------------------------* + * Rotation by multiples of 90 degrees * + *----------------------------------------------------------------------*/ +/*! + * \brief selRotateOrth() + * + * \param[in] sel + * \param[in] quads 0 - 4; number of 90 degree cw rotations + * \return seld, or NULL on error + */ +SEL * +selRotateOrth(SEL *sel, + l_int32 quads) +{ +l_int32 i, j, ni, nj, sx, sy, cx, cy, nsx, nsy, ncx, ncy, type; +SEL *seld; + + PROCNAME("selRotateOrth"); + + if (!sel) + return (SEL *)ERROR_PTR("sel not defined", procName, NULL); + if (quads < 0 || quads > 4) + return (SEL *)ERROR_PTR("quads not in {0,1,2,3,4}", procName, NULL); + if (quads == 0 || quads == 4) + return selCopy(sel); + + selGetParameters(sel, &sy, &sx, &cy, &cx); + if (quads == 1) { /* 90 degrees cw */ + nsx = sy; + nsy = sx; + ncx = sy - cy - 1; + ncy = cx; + } else if (quads == 2) { /* 180 degrees cw */ + nsx = sx; + nsy = sy; + ncx = sx - cx - 1; + ncy = sy - cy - 1; + } else { /* 270 degrees cw */ + nsx = sy; + nsy = sx; + ncx = cy; + ncy = sx - cx - 1; + } + seld = selCreateBrick(nsy, nsx, ncy, ncx, SEL_DONT_CARE); + if (sel->name) + seld->name = stringNew(sel->name); + + for (i = 0; i < sy; i++) { + for (j = 0; j < sx; j++) { + selGetElement(sel, i, j, &type); + if (quads == 1) { + ni = j; + nj = sy - i - 1; + } else if (quads == 2) { + ni = sy - i - 1; + nj = sx - j - 1; + } else { /* quads == 3 */ + ni = sx - j - 1; + nj = i; + } + selSetElement(seld, ni, nj, type); + } + } + + return seld; +} + + +/*----------------------------------------------------------------------* + * Sela and Sel serialized I/O * + *----------------------------------------------------------------------*/ +/*! + * \brief selaRead() + * + * \param[in] fname filename + * \return sela, or NULL on error + */ +SELA * +selaRead(const char *fname) +{ +FILE *fp; +SELA *sela; + + PROCNAME("selaRead"); + + if (!fname) + return (SELA *)ERROR_PTR("fname not defined", procName, NULL); + + if ((fp = fopenReadStream(fname)) == NULL) + return (SELA *)ERROR_PTR("stream not opened", procName, NULL); + if ((sela = selaReadStream(fp)) == NULL) { + fclose(fp); + return (SELA *)ERROR_PTR("sela not returned", procName, NULL); + } + fclose(fp); + + return sela; +} + + +/*! + * \brief selaReadStream() + * + * \param[in] fp file stream + * \return sela, or NULL on error + */ +SELA * +selaReadStream(FILE *fp) +{ +l_int32 i, n, version; +SEL *sel; +SELA *sela; + + PROCNAME("selaReadStream"); + + if (!fp) + return (SELA *)ERROR_PTR("stream not defined", procName, NULL); + + if (fscanf(fp, "\nSela Version %d\n", &version) != 1) + return (SELA *)ERROR_PTR("not a sela file", procName, NULL); + if (version != SEL_VERSION_NUMBER) + return (SELA *)ERROR_PTR("invalid sel version", procName, NULL); + if (fscanf(fp, "Number of Sels = %d\n\n", &n) != 1) + return (SELA *)ERROR_PTR("not a sela file", procName, NULL); + + if ((sela = selaCreate(n)) == NULL) + return (SELA *)ERROR_PTR("sela not made", procName, NULL); + sela->nalloc = n; + + for (i = 0; i < n; i++) { + if ((sel = selReadStream(fp)) == NULL) { + selaDestroy(&sela); + return (SELA *)ERROR_PTR("sel not read", procName, NULL); + } + selaAddSel(sela, sel, NULL, 0); + } + + return sela; +} + + +/*! + * \brief selRead() + * + * \param[in] fname filename + * \return sel, or NULL on error + */ +SEL * +selRead(const char *fname) +{ +FILE *fp; +SEL *sel; + + PROCNAME("selRead"); + + if (!fname) + return (SEL *)ERROR_PTR("fname not defined", procName, NULL); + + if ((fp = fopenReadStream(fname)) == NULL) + return (SEL *)ERROR_PTR("stream not opened", procName, NULL); + if ((sel = selReadStream(fp)) == NULL) { + fclose(fp); + return (SEL *)ERROR_PTR("sela not returned", procName, NULL); + } + fclose(fp); + + return sel; +} + + +/*! + * \brief selReadStream() + * + * \param[in] fp file stream + * \return sel, or NULL on error + */ +SEL * +selReadStream(FILE *fp) +{ +char selname[256]; +char linebuf[256]; +l_int32 sy, sx, cy, cx, i, j, version, ignore; +SEL *sel; + + PROCNAME("selReadStream"); + + if (!fp) + return (SEL *)ERROR_PTR("stream not defined", procName, NULL); + + if (fscanf(fp, " Sel Version %d\n", &version) != 1) + return (SEL *)ERROR_PTR("not a sel file", procName, NULL); + if (version != SEL_VERSION_NUMBER) + return (SEL *)ERROR_PTR("invalid sel version", procName, NULL); + + if (fgets(linebuf, sizeof(linebuf), fp) == NULL) + return (SEL *)ERROR_PTR("error reading into linebuf", procName, NULL); + sscanf(linebuf, " ------ %200s ------", selname); + + if (fscanf(fp, " sy = %d, sx = %d, cy = %d, cx = %d\n", + &sy, &sx, &cy, &cx) != 4) + return (SEL *)ERROR_PTR("dimensions not read", procName, NULL); + + if ((sel = selCreate(sy, sx, selname)) == NULL) + return (SEL *)ERROR_PTR("sel not made", procName, NULL); + selSetOrigin(sel, cy, cx); + + for (i = 0; i < sy; i++) { + ignore = fscanf(fp, " "); + for (j = 0; j < sx; j++) + ignore = fscanf(fp, "%1d", &sel->data[i][j]); + ignore = fscanf(fp, "\n"); + } + ignore = fscanf(fp, "\n"); + + return sel; +} + + +/*! + * \brief selaWrite() + * + * \param[in] fname filename + * \param[in] sela + * \return 0 if OK, 1 on error + */ +l_ok +selaWrite(const char *fname, + SELA *sela) +{ +FILE *fp; + + PROCNAME("selaWrite"); + + if (!fname) + return ERROR_INT("fname not defined", procName, 1); + if (!sela) + return ERROR_INT("sela not defined", procName, 1); + + if ((fp = fopenWriteStream(fname, "wb")) == NULL) + return ERROR_INT("stream not opened", procName, 1); + selaWriteStream(fp, sela); + fclose(fp); + + return 0; +} + + +/*! + * \brief selaWriteStream() + * + * \param[in] fp file stream + * \param[in] sela + * \return 0 if OK, 1 on error + */ +l_ok +selaWriteStream(FILE *fp, + SELA *sela) +{ +l_int32 i, n; +SEL *sel; + + PROCNAME("selaWriteStream"); + + if (!fp) + return ERROR_INT("stream not defined", procName, 1); + if (!sela) + return ERROR_INT("sela not defined", procName, 1); + + n = selaGetCount(sela); + fprintf(fp, "\nSela Version %d\n", SEL_VERSION_NUMBER); + fprintf(fp, "Number of Sels = %d\n\n", n); + for (i = 0; i < n; i++) { + if ((sel = selaGetSel(sela, i)) == NULL) + continue; + selWriteStream(fp, sel); + } + return 0; +} + + +/*! + * \brief selWrite() + * + * \param[in] fname filename + * \param[in] sel + * \return 0 if OK, 1 on error + */ +l_ok +selWrite(const char *fname, + SEL *sel) +{ +FILE *fp; + + PROCNAME("selWrite"); + + if (!fname) + return ERROR_INT("fname not defined", procName, 1); + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + + if ((fp = fopenWriteStream(fname, "wb")) == NULL) + return ERROR_INT("stream not opened", procName, 1); + selWriteStream(fp, sel); + fclose(fp); + + return 0; +} + + +/*! + * \brief selWriteStream() + * + * \param[in] fp file stream + * \param[in] sel + * \return 0 if OK, 1 on error + */ +l_ok +selWriteStream(FILE *fp, + SEL *sel) +{ +l_int32 sx, sy, cx, cy, i, j; + + PROCNAME("selWriteStream"); + + if (!fp) + return ERROR_INT("stream not defined", procName, 1); + if (!sel) + return ERROR_INT("sel not defined", procName, 1); + selGetParameters(sel, &sy, &sx, &cy, &cx); + + fprintf(fp, " Sel Version %d\n", SEL_VERSION_NUMBER); + fprintf(fp, " ------ %s ------\n", selGetName(sel)); + fprintf(fp, " sy = %d, sx = %d, cy = %d, cx = %d\n", sy, sx, cy, cx); + for (i = 0; i < sy; i++) { + fprintf(fp, " "); + for (j = 0; j < sx; j++) + fprintf(fp, "%d", sel->data[i][j]); + fprintf(fp, "\n"); + } + fprintf(fp, "\n"); + + return 0; +} + + +/*----------------------------------------------------------------------* + * Building custom hit-miss sels from compiled strings * + *----------------------------------------------------------------------*/ +/*! + * \brief selCreateFromString() + * + * \param[in] text + * \param[in] h, w height, width + * \param[in] name [optional] sel name; can be null + * \return sel of the given size, or NULL on error + * + * <pre> + * Notes: + * (1) The text is an array of chars (in row-major order) where + * each char can be one of the following: + * 'x': hit + * 'o': miss + * ' ': don't-care + * (2) When the origin falls on a hit or miss, use an upper case + * char (e.g., 'X' or 'O') to indicate it. When the origin + * falls on a don't-care, indicate this with a 'C'. + * The string must have exactly one origin specified. + * (3) The advantage of this method is that the text can be input + * in a format that shows the 2D layout of the Sel; e.g., + * \code + * static const char *seltext = "x " + * "x Oo " + * "x " + * "xxxxx"; + * \endcode + * </pre> + */ +SEL * +selCreateFromString(const char *text, + l_int32 h, + l_int32 w, + const char *name) +{ +SEL *sel; +l_int32 y, x, norig; +char ch; + + PROCNAME("selCreateFromString"); + + if (!text || text[0] == '\0') + return (SEL *)ERROR_PTR("text undefined or empty", procName, NULL); + if (h < 1) + return (SEL *)ERROR_PTR("height must be > 0", procName, NULL); + if (w < 1) + return (SEL *)ERROR_PTR("width must be > 0", procName, NULL); + if (strlen(text) != (size_t)w * h) + return (SEL *)ERROR_PTR("text size != w * h", procName, NULL); + + sel = selCreate(h, w, name); + norig = 0; + for (y = 0; y < h; ++y) { + for (x = 0; x < w; ++x) { + ch = *(text++); + switch (ch) + { + case 'X': + norig++; + selSetOrigin(sel, y, x); + /* fall through */ + case 'x': + selSetElement(sel, y, x, SEL_HIT); + break; + + case 'O': + norig++; + selSetOrigin(sel, y, x); + /* fall through */ + case 'o': + selSetElement(sel, y, x, SEL_MISS); + break; + + case 'C': + norig++; + selSetOrigin(sel, y, x); + /* fall through */ + case ' ': + selSetElement(sel, y, x, SEL_DONT_CARE); + break; + + case '\n': + /* ignored */ + continue; + + default: + selDestroy(&sel); + return (SEL *)ERROR_PTR("unknown char", procName, NULL); + } + } + } + if (norig != 1) { + L_ERROR("Exactly one origin must be specified; this string has %d\n", + procName, norig); + selDestroy(&sel); + } + + return sel; +} + + +/*! + * \brief selPrintToString() + * + * \param[in] sel + * \return str string; caller must free + * + * <pre> + * Notes: + * (1) This is an inverse function of selCreateFromString. + * It prints a textual representation of the SEL to a malloc'd + * string. The format is the same as selCreateFromString + * except that newlines are inserted into the output + * between rows. + * (2) This is useful for debugging. However, if you want to + * save some Sels in a file, put them in a Sela and write + * them out with selaWrite(). They can then be read in + * with selaRead(). + * </pre> + */ +char * +selPrintToString(SEL *sel) +{ +char is_center; +char *str, *strptr; +l_int32 type; +l_int32 sx, sy, cx, cy, x, y; + + PROCNAME("selPrintToString"); + + if (!sel) + return (char *)ERROR_PTR("sel not defined", procName, NULL); + + selGetParameters(sel, &sy, &sx, &cy, &cx); + if ((str = (char *)LEPT_CALLOC(1, sy * (sx + 1) + 1)) == NULL) + return (char *)ERROR_PTR("calloc fail for str", procName, NULL); + strptr = str; + + for (y = 0; y < sy; ++y) { + for (x = 0; x < sx; ++x) { + selGetElement(sel, y, x, &type); + is_center = (x == cx && y == cy); + switch (type) { + case SEL_HIT: + *(strptr++) = is_center ? 'X' : 'x'; + break; + case SEL_MISS: + *(strptr++) = is_center ? 'O' : 'o'; + break; + case SEL_DONT_CARE: + *(strptr++) = is_center ? 'C' : ' '; + break; + } + } + *(strptr++) = '\n'; + } + + return str; +} + + +/*----------------------------------------------------------------------* + * Building custom hit-miss sels from a simple file format * + *----------------------------------------------------------------------*/ +/*! + * \brief selaCreateFromFile() + * + * \param[in] filename + * \return sela, or NULL on error + * + * <pre> + * Notes: + * (1) The file contains a sequence of Sel descriptions. + * (2) Each Sel is formatted as follows: + * ~ Any number of comment lines starting with '#' are ignored + * ~ The next line contains the selname + * ~ The next lines contain the Sel data. They must be + * formatted similarly to the string format in + * selCreateFromString(), with each line beginning and + * ending with a double-quote, and showing the 2D layout. + * ~ Each Sel ends when a blank line, a comment line, or + * the end of file is reached. + * (3) See selCreateFromString() for a description of the string + * format for the Sel data. As an example, here are the lines + * of is a valid file for a single Sel. In the file, all lines + * are left-justified: + * # diagonal sel + * sel_5diag + * "x " + * " x " + * " X " + * " x " + * " x" + * </pre> + */ +SELA * +selaCreateFromFile(const char *filename) +{ +char *filestr, *line; +l_int32 i, n, first, last, nsel, insel; +size_t nbytes; +NUMA *nafirst, *nalast; +SARRAY *sa; +SEL *sel; +SELA *sela; + + PROCNAME("selaCreateFromFile"); + + if (!filename) + return (SELA *)ERROR_PTR("filename not defined", procName, NULL); + + filestr = (char *)l_binaryRead(filename, &nbytes); + sa = sarrayCreateLinesFromString(filestr, 1); + LEPT_FREE(filestr); + n = sarrayGetCount(sa); + sela = selaCreate(0); + + /* Find the start and end lines for each Sel. + * We allow the "blank" lines to be null strings or + * to have standard whitespace (' ','\t',\'n') or be '#'. */ + nafirst = numaCreate(0); + nalast = numaCreate(0); + insel = FALSE; + for (i = 0; i < n; i++) { + line = sarrayGetString(sa, i, L_NOCOPY); + if (!insel && + (line[0] != '\0' && line[0] != ' ' && + line[0] != '\t' && line[0] != '\n' && line[0] != '#')) { + numaAddNumber(nafirst, i); + insel = TRUE; + continue; + } + if (insel && + (line[0] == '\0' || line[0] == ' ' || + line[0] == '\t' || line[0] == '\n' || line[0] == '#')) { + numaAddNumber(nalast, i - 1); + insel = FALSE; + continue; + } + } + if (insel) /* fell off the end of the file */ + numaAddNumber(nalast, n - 1); + + /* Extract sels */ + nsel = numaGetCount(nafirst); + for (i = 0; i < nsel; i++) { + numaGetIValue(nafirst, i, &first); + numaGetIValue(nalast, i, &last); + if ((sel = selCreateFromSArray(sa, first, last)) == NULL) { + lept_stderr("Error reading sel from %d to %d\n", first, last); + selaDestroy(&sela); + sarrayDestroy(&sa); + numaDestroy(&nafirst); + numaDestroy(&nalast); + return (SELA *)ERROR_PTR("bad sela file", procName, NULL); + } + selaAddSel(sela, sel, NULL, 0); + } + + numaDestroy(&nafirst); + numaDestroy(&nalast); + sarrayDestroy(&sa); + return sela; +} + + +/*! + * \brief selCreateFromSArray() + * + * \param[in] sa + * \param[in] first line of sarray where Sel begins + * \param[in] last line of sarray where Sel ends + * \return sela, or NULL on error + * + * <pre> + * Notes: + * (1) The Sel contains the following lines: + * ~ The first line is the selname + * ~ The remaining lines contain the Sel data. They must + * be formatted similarly to the string format in + * selCreateFromString(), with each line beginning and + * ending with a double-quote, and showing the 2D layout. + * ~ 'last' gives the last line in the Sel data. + * (2) See selCreateFromString() for a description of the string + * format for the Sel data. As an example, here are the lines + * of is a valid file for a single Sel. In the file, all lines + * are left-justified: + * # diagonal sel + * sel_5diag + * "x " + * " x " + * " X " + * " x " + * " x" + * </pre> + */ +static SEL * +selCreateFromSArray(SARRAY *sa, + l_int32 first, + l_int32 last) +{ +char ch; +char *name, *line; +l_int32 n, len, i, w, h, y, x; +SEL *sel; + + PROCNAME("selCreateFromSArray"); + + if (!sa) + return (SEL *)ERROR_PTR("sa not defined", procName, NULL); + n = sarrayGetCount(sa); + if (first < 0 || first >= n || last <= first || last >= n) + return (SEL *)ERROR_PTR("invalid range", procName, NULL); + + name = sarrayGetString(sa, first, L_NOCOPY); + h = last - first; + line = sarrayGetString(sa, first + 1, L_NOCOPY); + len = strlen(line); + if (line[0] != '"' || line[len - 1] != '"') + return (SEL *)ERROR_PTR("invalid format", procName, NULL); + w = len - 2; + if ((sel = selCreate(h, w, name)) == NULL) + return (SEL *)ERROR_PTR("sel not made", procName, NULL); + for (i = first + 1; i <= last; i++) { + line = sarrayGetString(sa, i, L_NOCOPY); + y = i - first - 1; + for (x = 0; x < w; ++x) { + ch = line[x + 1]; /* skip the leading double-quote */ + switch (ch) + { + case 'X': + selSetOrigin(sel, y, x); /* set origin and hit */ + /* fall through */ + case 'x': + selSetElement(sel, y, x, SEL_HIT); + break; + + case 'O': + selSetOrigin(sel, y, x); /* set origin and miss */ + /* fall through */ + case 'o': + selSetElement(sel, y, x, SEL_MISS); + break; + + case 'C': + selSetOrigin(sel, y, x); /* set origin and don't-care */ + /* fall through */ + case ' ': + selSetElement(sel, y, x, SEL_DONT_CARE); + break; + + default: + selDestroy(&sel); + return (SEL *)ERROR_PTR("unknown char", procName, NULL); + } + } + } + + return sel; +} + + +/*----------------------------------------------------------------------* + * Making hit-only SELs from Pta and Pix * + *----------------------------------------------------------------------*/ +/*! + * \brief selCreateFromPta() + * + * \param[in] pta + * \param[in] cy, cx origin of sel + * \param[in] name [optional] sel name; can be null + * \return sel of minimum required size, or NULL on error + * + * <pre> + * Notes: + * (1) The origin and all points in the pta must be positive. + * </pre> + */ +SEL * +selCreateFromPta(PTA *pta, + l_int32 cy, + l_int32 cx, + const char *name) +{ +l_int32 i, n, x, y, w, h; +BOX *box; +SEL *sel; + + PROCNAME("selCreateFromPta"); + + if (!pta) + return (SEL *)ERROR_PTR("pta not defined", procName, NULL); + if (cy < 0 || cx < 0) + return (SEL *)ERROR_PTR("(cy, cx) not both >= 0", procName, NULL); + n = ptaGetCount(pta); + if (n == 0) + return (SEL *)ERROR_PTR("no pts in pta", procName, NULL); + + box = ptaGetBoundingRegion(pta); + boxGetGeometry(box, &x, &y, &w, &h); + boxDestroy(&box); + if (x < 0 || y < 0) + return (SEL *)ERROR_PTR("not all x and y >= 0", procName, NULL); + + sel = selCreate(y + h, x + w, name); + selSetOrigin(sel, cy, cx); + for (i = 0; i < n; i++) { + ptaGetIPt(pta, i, &x, &y); + selSetElement(sel, y, x, SEL_HIT); + } + + return sel; +} + + +/*! + * \brief selCreateFromPix() + * + * \param[in] pix + * \param[in] cy, cx origin of sel + * \param[in] name [optional] sel name; can be null + * \return sel, or NULL on error + * + * <pre> + * Notes: + * (1) The origin must be positive. + * (2) The pix must not exceed MaxPixTemplateSize in either dimension. + * and the total number of hits must not exceed MaxPixTemplateHits. + * </pre> + */ +SEL * +selCreateFromPix(PIX *pix, + l_int32 cy, + l_int32 cx, + const char *name) +{ +SEL *sel; +l_int32 i, j, w, h, d, nhits; +l_uint32 val; + + PROCNAME("selCreateFromPix"); + + if (!pix) + return (SEL *)ERROR_PTR("pix not defined", procName, NULL); + if (cy < 0 || cx < 0) + return (SEL *)ERROR_PTR("(cy, cx) not both >= 0", procName, NULL); + pixGetDimensions(pix, &w, &h, &d); + if (d != 1) + return (SEL *)ERROR_PTR("pix not 1 bpp", procName, NULL); + if (w > MaxPixTemplateSize || h > MaxPixTemplateSize) { + L_ERROR("pix template too large (w = %d, h = %d)\n", procName, w, h); + return NULL; + } + pixCountPixels(pix, &nhits, NULL); + if (nhits > MaxPixTemplateHits) { + L_ERROR("too many hits (%d) in pix template\n", procName, nhits); + return NULL; + } + + sel = selCreate(h, w, name); + selSetOrigin(sel, cy, cx); + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + pixGetPixel(pix, j, i, &val); + if (val) + selSetElement(sel, i, j, SEL_HIT); + } + } + + return sel; +} + + +/*----------------------------------------------------------------------* + * Making hit-miss sels from color Pix and image files * + *----------------------------------------------------------------------*/ +/*! + * + * selReadFromColorImage() + * + * \param[in] pathname + * \return sel if OK; NULL on error + * + * <pre> + * Notes: + * (1) Loads an image from a file and creates a (hit-miss) sel. + * (2) The sel name is taken from the pathname without the directory + * and extension. + * </pre> + */ +SEL * +selReadFromColorImage(const char *pathname) +{ +PIX *pix; +SEL *sel; +char *basename, *selname; + + PROCNAME("selReadFromColorImage"); + + splitPathAtExtension (pathname, &basename, NULL); + splitPathAtDirectory (basename, NULL, &selname); + LEPT_FREE(basename); + + if ((pix = pixRead(pathname)) == NULL) { + LEPT_FREE(selname); + return (SEL *)ERROR_PTR("pix not returned", procName, NULL); + } + if ((sel = selCreateFromColorPix(pix, selname)) == NULL) + L_ERROR("sel not made\n", procName); + + LEPT_FREE(selname); + pixDestroy(&pix); + return sel; +} + + +/*! + * + * selCreateFromColorPix() + * + * \param[in] pixs cmapped or rgb + * \param[in] selname [optional] sel name; can be null + * \return sel if OK, NULL on error + * + * <pre> + * Notes: + * (1) The sel size is given by the size of pixs. + * (2) In pixs, hits are represented by green pixels, misses by red + * pixels, and don't-cares by white pixels. + * (3) In pixs, there may be no misses, but there must be at least 1 hit. + * (4) At most there can be only one origin pixel, which is optionally + * specified by using a lower-intensity pixel: + * if a hit: dark green + * if a miss: dark red + * if a don't care: gray + * If there is no such pixel, the origin defaults to the approximate + * center of the sel. + * </pre> + */ +SEL * +selCreateFromColorPix(PIX *pixs, + const char *selname) +{ +PIXCMAP *cmap; +SEL *sel; +l_int32 hascolor, num_origins, nohits; +l_int32 w, h, d, i, j, red, green, blue; +l_uint32 pixval; + + PROCNAME("selCreateFromColorPix"); + + if (!pixs) + return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); + + hascolor = FALSE; + cmap = pixGetColormap(pixs); + if (cmap) + pixcmapHasColor(cmap, &hascolor); + pixGetDimensions(pixs, &w, &h, &d); + if (hascolor == FALSE && d != 32) + return (SEL *)ERROR_PTR("pixs has no color", procName, NULL); + + if ((sel = selCreate (h, w, NULL)) == NULL) + return (SEL *)ERROR_PTR ("sel not made", procName, NULL); + selSetOrigin (sel, h / 2, w / 2); /* default */ + selSetName(sel, selname); + + num_origins = 0; + nohits = TRUE; + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + pixGetPixel (pixs, j, i, &pixval); + + if (cmap) { + pixcmapGetColor (cmap, pixval, &red, &green, &blue); + } else { + red = GET_DATA_BYTE (&pixval, COLOR_RED); + green = GET_DATA_BYTE (&pixval, COLOR_GREEN); + blue = GET_DATA_BYTE (&pixval, COLOR_BLUE); + } + + if (red < 255 && green < 255 && blue < 255) { + num_origins++; + if (num_origins == 1) /* first one found */ + selSetOrigin (sel, i, j); + if (num_origins == 2) + L_WARNING("multiple origins in sel image\n", procName); + } + if (!red && green && !blue) { + nohits = FALSE; + selSetElement (sel, i, j, SEL_HIT); + } else if (red && !green && !blue) { + selSetElement (sel, i, j, SEL_MISS); + } else if (red && green && blue) { + selSetElement (sel, i, j, SEL_DONT_CARE); + } else { + selDestroy(&sel); + return (SEL *)ERROR_PTR("invalid color", procName, NULL); + } + } + } + + if (nohits) { + selDestroy(&sel); + return (SEL *)ERROR_PTR("no hits in sel", procName, NULL); + } + return sel; +} + + +/*! + * + * selaCreateFromColorPixa() + * + * \param[in] pixa color pixa representing the sels + * \param[in] sa sarray of sel names + * \return sel if OK, NULL on error + * + * <pre> + * Notes: + * (1) See notes in selCreateFromColorPix() + * (2) sa is required because all sels that are put in a sela + * must have a name. + * </pre> + */ +SELA * +selaCreateFromColorPixa(PIXA *pixa, + SARRAY *sa) +{ +char *str; +l_int32 i, n; +PIX *pix; +SEL *sel; +SELA *sela; + + PROCNAME("selaCreateFromColorPixa"); + + if (!pixa) + return (SELA *)ERROR_PTR("pixa not defined", procName, NULL); + if (!sa) + return (SELA *)ERROR_PTR("sa of sel names not defined", procName, NULL); + + n = pixaGetCount(pixa); + if ((sela = selaCreate(n)) == NULL) + return (SELA *)ERROR_PTR("sela not allocated", procName, NULL); + for (i = 0; i < n; i++) { + pix = pixaGetPix(pixa, i, L_CLONE); + str = sarrayGetString(sa, i, L_NOCOPY); + sel = selCreateFromColorPix(pix, str); + selaAddSel(sela, sel, NULL, L_INSERT); + pixDestroy(&pix); + } + return sela; +} + + +/*----------------------------------------------------------------------* + * Printable display of sel * + *----------------------------------------------------------------------*/ +/*! + * \brief selDisplayInPix() + * + * \param[in] sel + * \param[in] size of grid interiors; odd; minimum size of 13 is enforced + * \param[in] gthick grid thickness; minimum size of 2 is enforced + * \return pix display of sel, or NULL on error + * + * <pre> + * Notes: + * (1) This gives a visual representation of a general (hit-miss) sel. + * (2) The empty sel is represented by a grid of intersecting lines. + * (3) Three different patterns are generated for the sel elements: + * ~ hit (solid black circle) + * ~ miss (black ring; inner radius is radius2) + * ~ origin (cross, XORed with whatever is there) + * </pre> + */ +PIX * +selDisplayInPix(SEL *sel, + l_int32 size, + l_int32 gthick) +{ +l_int32 i, j, w, h, sx, sy, cx, cy, type, width; +l_int32 radius1, radius2, shift1, shift2, x0, y0; +PIX *pixd, *pix2, *pixh, *pixm, *pixorig; +PTA *pta1, *pta2, *pta1t, *pta2t; + + PROCNAME("selDisplayInPix"); + + if (!sel) + return (PIX *)ERROR_PTR("sel not defined", procName, NULL); + if (size < 13) { + L_WARNING("size < 13; setting to 13\n", procName); + size = 13; + } + if (size % 2 == 0) + size++; + if (gthick < 2) { + L_WARNING("grid thickness < 2; setting to 2\n", procName); + gthick = 2; + } + selGetParameters(sel, &sy, &sx, &cy, &cx); + w = size * sx + gthick * (sx + 1); + h = size * sy + gthick * (sy + 1); + pixd = pixCreate(w, h, 1); + + /* 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 hit and miss patterns */ + radius1 = (l_int32)(0.85 * ((size - 1) / 2.0) + 0.5); /* of hit */ + radius2 = (l_int32)(0.65 * ((size - 1) / 2.0) + 0.5); /* of inner miss */ + pta1 = generatePtaFilledCircle(radius1); + pta2 = generatePtaFilledCircle(radius2); + shift1 = (size - 1) / 2 - radius1; /* center circle in square */ + shift2 = (size - 1) / 2 - radius2; + pta1t = ptaTransform(pta1, shift1, shift1, 1.0, 1.0); + pta2t = ptaTransform(pta2, shift2, shift2, 1.0, 1.0); + pixh = pixGenerateFromPta(pta1t, size, size); /* hits */ + pix2 = pixGenerateFromPta(pta2t, size, size); + pixm = pixSubtract(NULL, pixh, pix2); + + /* Generate crossed lines for origin pattern */ + pixorig = pixCreate(size, size, 1); + width = size / 8; + pixRenderLine(pixorig, size / 2, (l_int32)(0.12 * size), + size / 2, (l_int32)(0.88 * size), + width, L_SET_PIXELS); + pixRenderLine(pixorig, (l_int32)(0.15 * size), size / 2, + (l_int32)(0.85 * size), size / 2, + width, L_FLIP_PIXELS); + pixRasterop(pixorig, size / 2 - width, size / 2 - width, + 2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0); + + /* Specialize origin pattern for this sel */ + selGetTypeAtOrigin(sel, &type); + if (type == SEL_HIT) + pixXor(pixorig, pixorig, pixh); + else if (type == SEL_MISS) + pixXor(pixorig, pixorig, pixm); + + /* Paste the patterns in */ + y0 = gthick; + for (i = 0; i < sy; i++) { + x0 = gthick; + for (j = 0; j < sx; j++) { + selGetElement(sel, i, j, &type); + if (i == cy && j == cx) /* origin */ + pixRasterop(pixd, x0, y0, size, size, PIX_SRC, pixorig, 0, 0); + else if (type == SEL_HIT) + pixRasterop(pixd, x0, y0, size, size, PIX_SRC, pixh, 0, 0); + else if (type == SEL_MISS) + pixRasterop(pixd, x0, y0, size, size, PIX_SRC, pixm, 0, 0); + x0 += size + gthick; + } + y0 += size + gthick; + } + + pixDestroy(&pix2); + pixDestroy(&pixh); + pixDestroy(&pixm); + pixDestroy(&pixorig); + ptaDestroy(&pta1); + ptaDestroy(&pta1t); + ptaDestroy(&pta2); + ptaDestroy(&pta2t); + return pixd; +} + + +/*! + * \brief selaDisplayInPix() + * + * \param[in] sela + * \param[in] size of grid interiors; odd; minimum size of 13 is enforced + * \param[in] gthick grid thickness; minimum size of 2 is enforced + * \param[in] spacing between sels, both horizontally and vertically + * \param[in] ncols number of sels per "line" + * \return pix display of all sels in sela, or NULL on error + * + * <pre> + * Notes: + * (1) This gives a visual representation of all the sels in a sela. + * (2) See notes in selDisplayInPix() for display params of each sel. + * (3) This gives the nicest results when all sels in the sela + * are the same size. + * </pre> + */ +PIX * +selaDisplayInPix(SELA *sela, + l_int32 size, + l_int32 gthick, + l_int32 spacing, + l_int32 ncols) +{ +l_int32 nsels, i, w, width; +PIX *pixt, *pixd; +PIXA *pixa; +SEL *sel; + + PROCNAME("selaDisplayInPix"); + + if (!sela) + return (PIX *)ERROR_PTR("sela not defined", procName, NULL); + if (size < 13) { + L_WARNING("size < 13; setting to 13\n", procName); + size = 13; + } + if (size % 2 == 0) + size++; + if (gthick < 2) { + L_WARNING("grid thickness < 2; setting to 2\n", procName); + gthick = 2; + } + if (spacing < 5) { + L_WARNING("spacing < 5; setting to 5\n", procName); + spacing = 5; + } + + /* Accumulate the pix of each sel */ + nsels = selaGetCount(sela); + pixa = pixaCreate(nsels); + for (i = 0; i < nsels; i++) { + sel = selaGetSel(sela, i); + pixt = selDisplayInPix(sel, size, gthick); + pixaAddPix(pixa, pixt, L_INSERT); + } + + /* Find the tiled output width, using just the first + * ncols pix in the pixa. If all pix have the same width, + * they will align properly in columns. */ + width = 0; + ncols = L_MIN(nsels, ncols); + for (i = 0; i < ncols; i++) { + pixt = pixaGetPix(pixa, i, L_CLONE); + pixGetDimensions(pixt, &w, NULL, NULL); + width += w; + pixDestroy(&pixt); + } + width += (ncols + 1) * spacing; /* add spacing all around as well */ + + pixd = pixaDisplayTiledInRows(pixa, 1, width, 1.0, 0, spacing, 0); + pixaDestroy(&pixa); + return pixd; +} |