Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/dewarp1.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/dewarp1.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1675 @@ +/*====================================================================* + - 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 dewarp1.c + * <pre> + * + * Basic operations and serialization + * + * Create/destroy dewarp + * L_DEWARP *dewarpCreate() + * L_DEWARP *dewarpCreateRef() + * void dewarpDestroy() + * + * Create/destroy dewarpa + * L_DEWARPA *dewarpaCreate() + * L_DEWARPA *dewarpaCreateFromPixacomp() + * void dewarpaDestroy() + * l_int32 dewarpaDestroyDewarp() + * + * Dewarpa insertion/extraction + * l_int32 dewarpaInsertDewarp() + * static l_int32 dewarpaExtendArraysToSize() + * L_DEWARP *dewarpaGetDewarp() + * + * Setting parameters to control rendering from the model + * l_int32 dewarpaSetCurvatures() + * l_int32 dewarpaUseBothArrays() + * l_int32 dewarpaSetCheckColumns() + * l_int32 dewarpaSetMaxDistance() + * + * Dewarp serialized I/O + * L_DEWARP *dewarpRead() + * L_DEWARP *dewarpReadStream() + * L_DEWARP *dewarpReadMem() + * l_int32 dewarpWrite() + * l_int32 dewarpWriteStream() + * l_int32 dewarpWriteMem() + * + * Dewarpa serialized I/O + * L_DEWARPA *dewarpaRead() + * L_DEWARPA *dewarpaReadStream() + * L_DEWARPA *dewarpaReadMem() + * l_int32 dewarpaWrite() + * l_int32 dewarpaWriteStream() + * l_int32 dewarpaWriteMem() + * + * + * Examples of usage + * ================= + * + * See dewarpaCreateFromPixacomp() for an example of the basic + * operations, starting from a set of 1 bpp images. + * + * Basic functioning to dewarp a specific single page: + * \code + * // Make the Dewarpa for the pages + * L_Dewarpa *dewa = dewarpaCreate(1, 30, 1, 15, 50); + * dewarpaSetCurvatures(dewa, -1, 50, -1, -1, -1, -1); + * dewarpaUseBothArrays(dewa, 1); // try to use both disparity + * // arrays for this example + * + * // Do the page: start with a binarized image + * Pix *pixb = "binarize"(pixs); + * // Initialize a Dewarp for this page (say, page 214) + * L_Dewarp *dew = dewarpCreate(pixb, 214); + * // Insert in Dewarpa and obtain parameters for building the model + * dewarpaInsertDewarp(dewa, dew); + * // Do the work + * dewarpBuildPageModel(dew, NULL); // no debugging + * // Optionally set rendering parameters + * // Apply model to the input pixs + * Pix *pixd; + * dewarpaApplyDisparity(dewa, 214, pixs, 255, 0, 0, &pixd, NULL); + * pixDestroy(&pixb); + * \endcode + * + * Basic functioning to dewarp many pages: + * \code + * // Make the Dewarpa for the set of pages; use fullres 1 bpp + * L_Dewarpa *dewa = dewarpaCreate(10, 30, 1, 15, 50); + * // Optionally set rendering parameters + * dewarpaSetCurvatures(dewa, -1, 30, -1, -1, -1, -1); + * dewarpaUseBothArrays(dewa, 0); // just use the vertical disparity + * // array for this example + * + * // Do first page: start with a binarized image + * Pix *pixb = "binarize"(pixs); + * // Initialize a Dewarp for this page (say, page 1) + * L_Dewarp *dew = dewarpCreate(pixb, 1); + * // Insert in Dewarpa and obtain parameters for building the model + * dewarpaInsertDewarp(dewa, dew); + * // Do the work + * dewarpBuildPageModel(dew, NULL); // no debugging + * dewarpMinimze(dew); // remove most heap storage + * pixDestroy(&pixb); + * + * // Do the other pages the same way + * ... + * + * // Apply models to each page; if the page model is invalid, + * // try to use a valid neighboring model. Note that the call + * // to dewarpaInsertRefModels() is optional, because it is called + * // by dewarpaApplyDisparity() on the first page it acts on. + * dewarpaInsertRefModels(dewa, 0, 1); // use debug flag to get more + * // detailed information about the page models + * [For each page, where pixs is the fullres image to be dewarped] { + * L_Dewarp *dew = dewarpaGetDewarp(dewa, pageno); + * if (dew) { // disparity model exists + * Pix *pixd; + * dewarpaApplyDisparity(dewa, pageno, pixs, 255, + * 0, 0, &pixd, NULL); + * dewarpMinimize(dew); // clean out the pix and fpix arrays + * // Squirrel pixd away somewhere ...) + * } + * } + * \endcode + * + * Basic functioning to dewarp a small set of pages, potentially + * using models from nearby pages: + * \code + * // (1) Generate a set of binarized images in the vicinity of the + * // pages to be dewarped. We will attempt to compute models + * // for pages from 'firstpage' to 'lastpage'. + * // Store the binarized images in a compressed array of + * // size 'n', where 'n' is the number of images to be stored, + * // and where the offset is the first page. + * PixaComp *pixac = pixacompCreateInitialized(n, firstpage, NULL, + * IFF_TIFF_G4); + * for (i = firstpage; i <= lastpage; i++) { + * Pix *pixb = "binarize"(pixs); + * pixacompReplacePix(pixac, i, pixb, IFF_TIFF_G4); + * pixDestroy(&pixb); + * } + * + * // (2) Make the Dewarpa for the pages. + * L_Dewarpa *dewa = + * dewarpaCreateFromPixacomp(pixac, 30, 15, 20); + * dewarpaUseBothArrays(dewa, 1); // try to use both disparity arrays + * // in this example + * + * // (3) Finally, apply the models. For page 'firstpage' with image pixs: + * L_Dewarp *dew = dewarpaGetDewarp(dewa, firstpage); + * if (dew) { // disparity model exists + * Pix *pixd; + * dewarpaApplyDisparity(dewa, firstpage, pixs, 255, 0, 0, &pixd, NULL); + * dewarpMinimize(dew); + * } + * \endcode + * + * Because in general some pages will not have enough text to build a + * model, we fill in for those pages with a reference to the page + * model to use. Both the target page and the reference page must + * have the same parity. We can also choose to use either a partial model + * (with only vertical disparity) or the full model of a nearby page. + * + * Minimizing the data in a model by stripping out images, + * numas, and full resolution disparity arrays: + * dewarpMinimize(dew); + * This can be done at any time to save memory. Serialization does + * not use the data that is stripped. + * + * You can apply any model (in a dew), stripped or not, to another image: + * \code + * // For all pages with invalid models, assign the nearest valid + * // page model with same parity. + * dewarpaInsertRefModels(dewa, 0, 0); + * // You can then apply to 'newpix' the page model that was assigned + * // to 'pageno', giving the result in pixd: + * Pix *pixd; + * dewarpaApplyDisparity(dewa, pageno, newpix, 255, 0, 0, &pixd, NULL); + * \endcode + * + * You can apply the disparity arrays to a deliberately undercropped + * image. Suppose that you undercrop by (left, right, top, bot), so + * that the disparity arrays are aligned with their origin at (left, top). + * Dewarp the undercropped image with: + * \code + * Pix *pixd; + * dewarpaApplyDisparity(dewa, pageno, undercropped_pix, 255, + * left, top, &pixd, NULL); + * \endcode + * + * Description of the approach to analyzing page image distortion + * ============================================================== + * + * When a book page is scanned, there are several possible causes + * for the text lines to appear to be curved: + * (1) A barrel (fish-eye) effect because the camera is at + * a finite distance from the page. Take the normal from + * the camera to the page (the 'optic axis'). Lines on + * the page "below" this point will appear to curve upward + * (negative curvature); lines "above" this will curve downward. + * (2) Radial distortion from the camera lens. Probably not + * a big factor. + * (3) Local curvature of the page in to (or out of) the image + * plane (which is perpendicular to the optic axis). + * This has no effect if the page is flat. + * + * In the following, the optic axis is in the z direction and is + * perpendicular to the xy plane;, the book is assumed to be aligned + * so that y is approximately along the binding. + * The goal is to compute the "disparity" field, D(x,y), which + * is actually a vector composed of the horizontal and vertical + * disparity fields H(x,y) and V(x,y). Each of these is a local + * function that gives the amount each point in the image is + * required to move in order to rectify the horizontal and vertical + * lines. It would also be nice to "flatten" the page to compensate + * for effect (3), foreshortening due to bending of the page into + * the z direction, but that is more difficult. + * + * Effects (1) and (2) can be directly compensated by calibrating + * the scene, using a flat page with horizontal and vertical lines. + * Then H(x,y) and V(x,y) can be found as two (non-parametric) arrays + * of values. Suppose this has been done. Then the remaining + * distortion is due to (3). + * + * We consider the simple situation where the page bending is independent + * of y, and is described by alpha(x), where alpha is the angle between + * the normal to the page and the optic axis. cos(alpha(x)) is the local + * compression factor of the page image in the horizontal direction, at x. + * Thus, if we know alpha(x), we can compute the disparity H(x) required + * to flatten the image by simply integrating 1/cos(alpha), and we could + * compute the remaining disparities, H(x,y) and V(x,y), from the + * page content, as described below. Unfortunately, we don't know + * alpha. What do we know? If there are horizontal text lines + * on the page, we can compute the vertical disparity, V(x,y), which + * is the local translation required to make the text lines parallel + * to the rasters. If the margins are left and right aligned, we can + * also estimate the horizontal disparity, H(x,y), required to have + * uniform margins. All that can be done from the image alone, + * assuming we have text lines covering a sufficient part of the page. + * + * What about alpha(x)? The basic question relating to (3) is this: + * + * Is it possible, using the shape of the text lines alone, + * to compute both the vertical and horizontal disparity fields? + * + * The underlying problem is to separate the line curvature effects due + * to the camera view from those due to actual bending of the page. + * I believe the proper way to do this is to make some measurements + * based on the camera setup, which will depend mostly on the distance + * of the camera from the page, and to a smaller extent on the location + * of the optic axis with respect to the page. + * + * Here is the procedure. Photograph a page with a fine 2D line grid + * several times, each with a different slope near the binding. + * This can be done by placing the grid page on books that have + * different shapes z(x) near the binding. For each one you can + * measure, near the binding: + * (1) ds/dy, the vertical rate of change of slope of the horizontal lines + * (2) the local horizontal compression of the vertical lines due + * to the page angle dz/dx. + * As mentioned above, the local horizontal compression is simply + * cos(dz/dx). But the measurement you can make on an actual book + * page is (1). The difficulty is to generate (2) from (1). + * + * Back to the procedure. The function in (1), ds/dy, likely needs + * to be measured at a few y locations, because the relation + * between (1) and (2) may weakly depend on the y-location with + * respect to the y-coordinate of the optic axis of the camera. + * From these measurements you can determine, for the camera setup + * that you have, the local horizontal compression, cos(dz/dx), as a + * function of the both vertical location (y) and your measured vertical + * derivative of the text line slope there, ds/dy. Then with + * appropriate smoothing of your measured values, you can set up a + * horizontal disparity array to correct for the compression due + * to dz/dx. + * + * Now consider V(x,0) and V(x,h), the vertical disparity along + * the top and bottom of the image. With a little thought you + * can convince yourself that the local foreshortening, + * as a function of x, is proportional to the difference + * between the slope of V(x,0) and V(x,h). The horizontal + * disparity can then be computed by integrating the local foreshortening + * over x. Integration of the slope of V(x,0) and V(x,h) gives + * the vertical disparity itself. We have to normalize to h, the + * height of the page. So the very simple result is that + * + * H(x) ~ (V(x,0) - V(x,h)) / h [1] + * + * which is easily computed. There is a proportionality constant + * that depends on the ratio of h to the distance to the camera. + * Can we actually believe this for the case where the bending + * is independent of y? I believe the answer is yes, + * as long as you first remove the apparent distortion due + * to the camera being at a finite distance. + * + * If you know the intersection of the optical axis with the page + * and the distance to the camera, and if the page is perpendicular + * to the optic axis, you can compute the horizontal and vertical + * disparities due to (1) and (2) and remove them. The resulting + * distortion should be entirely due to bending (3), for which + * the relation + * + * Hx(x) dx = C * ((Vx(x,0) - Vx(x, h))/h) dx [2] + * + * holds for each point in x (Hx and Vx are partial derivatives w/rt x). + * Integrating over x, and using H(0) = 0, we get the result [1]. + * + * I believe this result holds differentially for each value of y, so + * that in the case where the bending is not independent of y, + * the expression (V(x,0) - V(x,h)) / h goes over to Vy(x,y). Then + * + * H(x,y) = Integral(0,x) (Vyx(x,y) dx) [3] + * + * where Vyx() is the partial derivative of V w/rt both x and y. + * + * It would be nice if there were a simple mathematical relation between + * the horizontal and vertical disparities for the situation + * where the paper bends without stretching or kinking. + * I had hoped to get a relation between H and V, such as + * Hx(x,y) ~ Vy(x,y), which would imply that H and V are real + * and imaginary parts of a complex potential, each of which + * satisfy the laplace equation. But then the gradients of the + * two potentials would be normal, and that does not appear to be the case. + * Thus, the questions of proving the relations above (for small bending), + * or finding a simpler relation between H and V than those equations, + * remain open. So far, we have only used [1] for the horizontal + * disparity H(x). + * + * In the version of the code that follows, we first use text lines + * to find V(x,y). Then, we try to compute H(x,y) that will align + * the text vertically on the left and right margins. This is not + * always possible -- sometimes the right margin is not right justified. + * By default, we don't require the horizontal disparity to have a + * valid page model for dewarping a page, but this requirement can + * be forced using dewarpaUseFullModel(). + * + * As described above, one can add a y-independent component of + * the horizontal disparity H(x) to counter the foreshortening + * effect due to the bending of the page near the binding. + * This requires widening the image on the side near the binding, + * and we do not provide this option here. However, we do provide + * a function that will generate this disparity field: + * fpixExtraHorizDisparity() + * + * Here is the basic outline for building the disparity arrays. + * + * (1) Find lines going approximately through the center of the + * text in each text line. Accept only lines that are + * close in length to the longest line. + * (2) Use these lines to generate a regular and highly subsampled + * vertical disparity field V(x,y). + * (3) Interpolate this to generate a full resolution vertical + * disparity field. + * (4) For lines that are sufficiently long, assume they are approximately + * left and right-justified, and construct a highly subsampled + * horizontal disparity field H(x,y) that will bring them into alignment. + * (5) Interpolate this to generate a full resolution horizontal + * disparity field. + * (6) Apply the vertical dewarping, followed by the horizontal dewarping. + * + * Step (1) is clearly described by the code in pixGetTextlineCenters(). + * + * Steps (2) and (3) follow directly from the data in step (1), + * and constitute the bulk of the work done in dewarpBuildPageModel(). + * Virtually all the noise in the data is smoothed out by doing + * least-square quadratic fits, first horizontally to the data + * points representing the text line centers, and then vertically. + * The trick is to sample these lines on a regular grid. + * First each horizontal line is sampled at equally spaced + * intervals horizontally. We thus get a set of points, + * one in each line, that are vertically aligned, and + * the data we represent is the vertical distance of each point + * from the min or max value on the curve, depending on the + * sign of the curvature component. Each of these vertically + * aligned sets of points constitutes a sampled vertical disparity, + * and we do a LS quartic fit to each of them, followed by + * vertical sampling at regular intervals. We now have a subsampled + * grid of points, all equally spaced, giving at each point the local + * vertical disparity. Finally, the full resolution vertical disparity + * is formed by interpolation. All the least square fits do a + * great job of smoothing everything out, as can be observed by + * the contour maps that are generated for the vertical disparity field. + * + * Steps (4) through (6) again use the line data in step (1). + * By default, we do separate quadratic fits to the left and right + * line edges. There is also the option to do linear fits to the + * line edges, which typically does not give as good a fit, but is + * safer for some pages that have text in the margins, or have multiple + * columns of text with a large space between the columns. There is + * an option, which is the default, to check for multiple columns and + * if found to skip dewarping based on the line edges -- we compute but + * do not use the horizontal disparity array. + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <math.h> +#include "allheaders.h" + +static l_int32 dewarpaExtendArraysToSize(L_DEWARPA *dewa, l_int32 size); + + /* Parameter values used in dewarpaCreate() */ +static const l_int32 InitialPtrArraySize = 20; /* n'import quoi */ +static const l_int32 MaxPtrArraySize = 10000; +static const l_int32 DefaultArraySampling = 30; +static const l_int32 MinArraySampling = 8; +static const l_int32 DefaultMinLines = 15; +static const l_int32 MinMinLines = 4; +static const l_int32 DefaultMaxRefDist = 16; +static const l_int32 DefaultUseBoth = TRUE; +static const l_int32 DefaultCheckColumns = TRUE; + + /* Parameter values used in dewarpaSetCurvatures() */ +static const l_int32 DefaultMaxLineCurv = 150; +static const l_int32 DefaultMinDiffLineCurv = 0; +static const l_int32 DefaultMaxDiffLineCurv = 170; +static const l_int32 DefaultMaxEdgeCurv = 50; +static const l_int32 DefaultMaxDiffEdgeCurv = 40; +static const l_int32 DefaultMaxEdgeSlope = 80; + +/*----------------------------------------------------------------------* + * Create/destroy Dewarp * + *----------------------------------------------------------------------*/ +/*! + * \brief dewarpCreate() + * + * \param[in] pixs 1 bpp + * \param[in] pageno page number + * \return dew or NULL on error + * + * <pre> + * Notes: + * (1) The input pixs is either full resolution or 2x reduced. + * (2) The page number is typically 0-based. If scanned from a book, + * the even pages are usually on the left. Disparity arrays + * built for even pages should only be applied to even pages. + * </pre> + */ +L_DEWARP * +dewarpCreate(PIX *pixs, + l_int32 pageno) +{ +L_DEWARP *dew; + + if (!pixs) + return (L_DEWARP *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 1) + return (L_DEWARP *)ERROR_PTR("pixs not 1 bpp", __func__, NULL); + + dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP)); + dew->pixs = pixClone(pixs); + dew->pageno = pageno; + dew->w = pixGetWidth(pixs); + dew->h = pixGetHeight(pixs); + return dew; +} + + +/*! + * \brief dewarpCreateRef() + * + * \param[in] pageno this page number + * \param[in] refpage page number of dewarp disparity arrays to be used + * \return dew or NULL on error + * + * <pre> + * Notes: + * (1) This specifies which dewarp struct should be used for + * the given page. It is placed in dewarpa for pages + * for which no model can be built. + * (2) This page and the reference page have the same parity and + * the reference page is the closest page with a disparity model + * to this page. + * </pre> + */ +L_DEWARP * +dewarpCreateRef(l_int32 pageno, + l_int32 refpage) +{ +L_DEWARP *dew; + + dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP)); + dew->pageno = pageno; + dew->hasref = 1; + dew->refpage = refpage; + return dew; +} + + +/*! + * \brief dewarpDestroy() + * + * \param[in,out] pdew will be set to null before returning + * \return void + */ +void +dewarpDestroy(L_DEWARP **pdew) +{ +L_DEWARP *dew; + + if (pdew == NULL) { + L_WARNING("ptr address is null!\n", __func__); + return; + } + if ((dew = *pdew) == NULL) + return; + + pixDestroy(&dew->pixs); + fpixDestroy(&dew->sampvdispar); + fpixDestroy(&dew->samphdispar); + fpixDestroy(&dew->sampydispar); + fpixDestroy(&dew->fullvdispar); + fpixDestroy(&dew->fullhdispar); + fpixDestroy(&dew->fullydispar); + numaDestroy(&dew->namidys); + numaDestroy(&dew->nacurves); + LEPT_FREE(dew); + *pdew = NULL; +} + + +/*----------------------------------------------------------------------* + * Create/destroy Dewarpa * + *----------------------------------------------------------------------*/ +/*! + * \brief dewarpaCreate() + * + * \param[in] nptrs number of dewarp page ptrs; typ. the number of pages + * \param[in] sampling use 0 for default value; the minimum allowed is 8 + * \param[in] redfactor of input images: 1 is full res; 2 is 2x reduced + * \param[in] minlines minimum number of lines to accept; use 0 for default + * \param[in] maxdist for locating reference disparity; use -1 for default + * \return dewa or NULL on error + * + * <pre> + * Notes: + * (1) The sampling, minlines and maxdist parameters will be + * applied to all images. + * (2) The sampling factor is used for generating the disparity arrays + * from the input image. For 2x reduced input, use a sampling + * factor that is half the sampling you want on the full resolution + * images. + * (3) Use %redfactor = 1 for full resolution; 2 for 2x reduction. + * All input images must be at one of these two resolutions. + * (4) %minlines is the minimum number of nearly full-length lines + * required to generate a vertical disparity array. The default + * number is 15. Use a smaller number to accept a questionable + * array, but not smaller than 4. + * (5) When a model can't be built for a page, it looks up to %maxdist + * in either direction for a valid model with the same page parity. + * Use -1 for the default value of %maxdist; use 0 to avoid using + * a ref model. + * (6) The ptr array is expanded as necessary to accommodate page images. + * </pre> + */ +L_DEWARPA * +dewarpaCreate(l_int32 nptrs, + l_int32 sampling, + l_int32 redfactor, + l_int32 minlines, + l_int32 maxdist) +{ +L_DEWARPA *dewa; + + if (nptrs <= 0) + nptrs = InitialPtrArraySize; + if (nptrs > MaxPtrArraySize) + return (L_DEWARPA *)ERROR_PTR("too many pages", __func__, NULL); + if (redfactor != 1 && redfactor != 2) + return (L_DEWARPA *)ERROR_PTR("redfactor not in {1,2}", + __func__, NULL); + if (sampling == 0) { + sampling = DefaultArraySampling; + } else if (sampling < MinArraySampling) { + L_WARNING("sampling too small; setting to %d\n", __func__, + MinArraySampling); + sampling = MinArraySampling; + } + if (minlines == 0) { + minlines = DefaultMinLines; + } else if (minlines < MinMinLines) { + L_WARNING("minlines too small; setting to %d\n", __func__, + MinMinLines); + minlines = DefaultMinLines; + } + if (maxdist < 0) + maxdist = DefaultMaxRefDist; + + dewa = (L_DEWARPA *)LEPT_CALLOC(1, sizeof(L_DEWARPA)); + dewa->dewarp = (L_DEWARP **)LEPT_CALLOC(nptrs, sizeof(L_DEWARPA *)); + dewa->dewarpcache = (L_DEWARP **)LEPT_CALLOC(nptrs, sizeof(L_DEWARPA *)); + if (!dewa->dewarp || !dewa->dewarpcache) { + dewarpaDestroy(&dewa); + return (L_DEWARPA *)ERROR_PTR("dewarp ptrs not made", __func__, NULL); + } + dewa->nalloc = nptrs; + dewa->sampling = sampling; + dewa->redfactor = redfactor; + dewa->minlines = minlines; + dewa->maxdist = maxdist; + dewa->max_linecurv = DefaultMaxLineCurv; + dewa->min_diff_linecurv = DefaultMinDiffLineCurv; + dewa->max_diff_linecurv = DefaultMaxDiffLineCurv; + dewa->max_edgeslope = DefaultMaxEdgeSlope; + dewa->max_edgecurv = DefaultMaxEdgeCurv; + dewa->max_diff_edgecurv = DefaultMaxDiffEdgeCurv; + dewa->check_columns = DefaultCheckColumns; + dewa->useboth = DefaultUseBoth; + return dewa; +} + + +/*! + * \brief dewarpaCreateFromPixacomp() + * + * \param[in] pixac pixacomp of G4, 1 bpp images; with 1x1x1 placeholders + * \param[in] useboth 0 for only vert disparity; 1 for both vert and horiz + * \param[in] sampling use -1 or 0 for default value; otherwise minimum of 5 + * \param[in] minlines minimum number of lines to accept; e.g., 10 + * \param[in] maxdist for locating reference disparity; use -1 for default + * \return dewa or NULL on error + * + * <pre> + * Notes: + * (1) The returned dewa has disparity arrays calculated and + * is ready for serialization or for use in dewarping. + * (2) The sampling, minlines and maxdist parameters are + * applied to all images. See notes in dewarpaCreate() for details. + * (3) The pixac is full. Placeholders, if any, are w=h=d=1 images, + * and the real input images are 1 bpp at full resolution. + * They are assumed to be cropped to the actual page regions, + * and may be arbitrarily sparse in the array. + * (4) The output dewarpa is indexed by the page number. + * The offset in the pixac gives the mapping between the + * array index in the pixac and the page number. + * (5) This adds the ref page models. + * (6) This can be used to make models for any desired set of pages. + * The direct models are only made for pages with images in + * the pixacomp; the ref models are made for pages of the + * same parity within %maxdist of the nearest direct model. + * </pre> + */ +L_DEWARPA * +dewarpaCreateFromPixacomp(PIXAC *pixac, + l_int32 useboth, + l_int32 sampling, + l_int32 minlines, + l_int32 maxdist) +{ +l_int32 i, nptrs, pageno; +L_DEWARP *dew; +L_DEWARPA *dewa; +PIX *pixt; + + if (!pixac) + return (L_DEWARPA *)ERROR_PTR("pixac not defined", __func__, NULL); + + nptrs = pixacompGetCount(pixac); + if ((dewa = dewarpaCreate(pixacompGetOffset(pixac) + nptrs, + sampling, 1, minlines, maxdist)) == NULL) + return (L_DEWARPA *)ERROR_PTR("dewa not made", __func__, NULL); + dewarpaUseBothArrays(dewa, useboth); + + for (i = 0; i < nptrs; i++) { + pageno = pixacompGetOffset(pixac) + i; /* index into pixacomp */ + pixt = pixacompGetPix(pixac, pageno); + if (pixt && (pixGetWidth(pixt) > 1)) { + dew = dewarpCreate(pixt, pageno); + pixDestroy(&pixt); + if (!dew) { + ERROR_INT("unable to make dew!", __func__, 1); + continue; + } + + /* Insert into dewa for this page */ + dewarpaInsertDewarp(dewa, dew); + + /* Build disparity arrays for this page */ + dewarpBuildPageModel(dew, NULL); + if (!dew->vsuccess) { /* will need to use model from nearby page */ + dewarpaDestroyDewarp(dewa, pageno); + L_ERROR("unable to build model for page %d\n", __func__, i); + continue; + } + /* Remove all extraneous data */ + dewarpMinimize(dew); + } + pixDestroy(&pixt); + } + dewarpaInsertRefModels(dewa, 0, 0); + + return dewa; +} + + +/*! + * \brief dewarpaDestroy() + * + * \param[in,out] pdewa will be set to null before returning + * \return void + */ +void +dewarpaDestroy(L_DEWARPA **pdewa) +{ +l_int32 i; +L_DEWARP *dew; +L_DEWARPA *dewa; + + if (pdewa == NULL) { + L_WARNING("ptr address is null!\n", __func__); + return; + } + if ((dewa = *pdewa) == NULL) + return; + + for (i = 0; i < dewa->nalloc; i++) { + if ((dew = dewa->dewarp[i]) != NULL) + dewarpDestroy(&dew); + if ((dew = dewa->dewarpcache[i]) != NULL) + dewarpDestroy(&dew); + } + numaDestroy(&dewa->namodels); + numaDestroy(&dewa->napages); + + LEPT_FREE(dewa->dewarp); + LEPT_FREE(dewa->dewarpcache); + LEPT_FREE(dewa); + *pdewa = NULL; +} + + +/*! + * \brief dewarpaDestroyDewarp() + * + * \param[in] dewa + * \param[in] pageno of dew to be destroyed + * \return 0 if OK, 1 on error + */ +l_ok +dewarpaDestroyDewarp(L_DEWARPA *dewa, + l_int32 pageno) +{ +L_DEWARP *dew; + + if (!dewa) + return ERROR_INT("dewa or dew not defined", __func__, 1); + if (pageno < 0 || pageno > dewa->maxpage) + return ERROR_INT("page out of bounds", __func__, 1); + if ((dew = dewa->dewarp[pageno]) == NULL) + return ERROR_INT("dew not defined", __func__, 1); + + dewarpDestroy(&dew); + dewa->dewarp[pageno] = NULL; + return 0; +} + + +/*----------------------------------------------------------------------* + * Dewarpa insertion/extraction * + *----------------------------------------------------------------------*/ +/*! + * \brief dewarpaInsertDewarp() + * + * \param[in] dewa + * \param[in] dew to be added + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This inserts the dewarp into the array, which now owns it. + * It also keeps track of the largest page number stored. + * It must be done before the disparity model is built. + * (2) Note that this differs from the usual method of filling out + * arrays in leptonica, where the arrays are compact and + * new elements are typically added to the end. Here, + * the dewarp can be added anywhere, even beyond the initial + * allocation. + * </pre> + */ +l_ok +dewarpaInsertDewarp(L_DEWARPA *dewa, + L_DEWARP *dew) +{ +l_int32 pageno, n, newsize; +L_DEWARP *prevdew; + + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + if (!dew) + return ERROR_INT("dew not defined", __func__, 1); + + dew->dewa = dewa; + pageno = dew->pageno; + if (pageno > MaxPtrArraySize) + return ERROR_INT("too many pages", __func__, 1); + if (pageno > dewa->maxpage) + dewa->maxpage = pageno; + dewa->modelsready = 0; /* force re-evaluation at application time */ + + /* Extend ptr array if necessary */ + n = dewa->nalloc; + newsize = n; + if (pageno >= 2 * n) + newsize = 2 * pageno; + else if (pageno >= n) + newsize = 2 * n; + if (newsize > n) { + if (dewarpaExtendArraysToSize(dewa, newsize)) + return ERROR_INT("extension failed", __func__, 1); + } + + if ((prevdew = dewarpaGetDewarp(dewa, pageno)) != NULL) + dewarpDestroy(&prevdew); + dewa->dewarp[pageno] = dew; + + dew->sampling = dewa->sampling; + dew->redfactor = dewa->redfactor; + dew->minlines = dewa->minlines; + + /* Get the dimensions of the sampled array. This will be + * stored in an fpix, and the input resolution version is + * guaranteed to be larger than pixs. However, if you + * want to apply the disparity to an image with a width + * w > nx * s - 2 * s + 2 + * you will need to extend the input res fpix. + * And similarly for h. */ + dew->nx = (dew->w + 2 * dew->sampling - 2) / dew->sampling; + dew->ny = (dew->h + 2 * dew->sampling - 2) / dew->sampling; + return 0; +} + + +/*! + * \brief dewarpaExtendArraysToSize() + * + * \param[in] dewa + * \param[in] size new size of dewarpa array + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) If necessary, reallocs main and cache dewarpa ptr arrays to %size. + * </pre> + */ +static l_int32 +dewarpaExtendArraysToSize(L_DEWARPA *dewa, + l_int32 size) +{ + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + if (size > dewa->nalloc) { + if ((dewa->dewarp = (L_DEWARP **)reallocNew((void **)&dewa->dewarp, + sizeof(L_DEWARP *) * dewa->nalloc, + size * sizeof(L_DEWARP *))) == NULL) + return ERROR_INT("new ptr array not returned", __func__, 1); + if ((dewa->dewarpcache = + (L_DEWARP **)reallocNew((void **)&dewa->dewarpcache, + sizeof(L_DEWARP *) * dewa->nalloc, + size * sizeof(L_DEWARP *))) == NULL) + return ERROR_INT("new ptr cache array not returned", __func__, 1); + dewa->nalloc = size; + } + return 0; +} + + +/*! + * \brief dewarpaGetDewarp() + * + * \param[in] dewa populated with dewarp structs for pages + * \param[in] index into dewa: this is the pageno + * \return dew handle; still owned by dewa, or NULL on error + */ +L_DEWARP * +dewarpaGetDewarp(L_DEWARPA *dewa, + l_int32 index) +{ + if (!dewa) + return (L_DEWARP *)ERROR_PTR("dewa not defined", __func__, NULL); + if (index < 0 || index > dewa->maxpage) { + L_ERROR("index = %d is invalid; max index = %d\n", + __func__, index, dewa->maxpage); + return NULL; + } + + return dewa->dewarp[index]; +} + + +/*----------------------------------------------------------------------* + * Setting parameters to control rendering from the model * + *----------------------------------------------------------------------*/ +/*! + * \brief dewarpaSetCurvatures() + * + * \param[in] dewa + * \param[in] max_linecurv -1 for default + * \param[in] min_diff_linecurv -1 for default; 0 to accept all models + * \param[in] max_diff_linecurv -1 for default + * \param[in] max_edgecurv -1 for default; 0 to fit a line + * \param[in] max_diff_edgecurv -1 for default + * \param[in] max_edgeslope -1 for default + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Approximating the line by a quadratic, the coefficient + * of the quadratic term is the curvature, and distance + * units are in pixels (of course). Curvatures are very + * small, so we multiply by 10^6 and express the constraints + * on the model curvatures in micro-units. The slope parameter + * is multiplied by 10^3 and expressed in milli-units. + * (2) This sets five curvature thresholds and a slope threshold + * for dewarping to take place. Use -1 for default values. + * * max_linecurv: the maximum absolute value of the vertical + * disparity line curvatures. + * * min_diff_linecurv: the minimum absolute value of the + * largest difference in vertical disparity line curvatures. + * Use a value of 0 to accept all models. + * * max_diff_linecurv: the maximum absolute value of the largest + * difference in vertical disparity line curvatures. + * * max_edgecurv: the maximum absolute value of the left and right + * edge curvature for the horizontal disparity. Use a value of + * zero to fit a straight line (zero curvature). + * * max_diff_edgecurv: the maximum absolute value of the difference + * between left and right edge curvature for the horizontal + * disparity. This value is ignored if max_edgecurve = 0. + * * max_edgeslope: the maximum slope coefficient for left and + * right line edges. + * (3) An image with a line curvature less than about 0.00001 + * has fairly straight textlines. This is 10 micro-units. + * (4) For example, if %max_linecurv == 100, this would prevent dewarping + * if any of the lines has a curvature exceeding 100 micro-units. + * A model having maximum line curvature larger than about 150 + * micro-units should probably not be used. + * (5) A model having a left or right edge curvature larger than + * about 50 micro-units should probably not be used. Set the + * parameter max_edgecurv = 0 for a linear LSF. + * </pre> + */ +l_ok +dewarpaSetCurvatures(L_DEWARPA *dewa, + l_int32 max_linecurv, + l_int32 min_diff_linecurv, + l_int32 max_diff_linecurv, + l_int32 max_edgecurv, + l_int32 max_diff_edgecurv, + l_int32 max_edgeslope) +{ + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + if (max_linecurv == -1) + dewa->max_linecurv = DefaultMaxLineCurv; + else + dewa->max_linecurv = L_ABS(max_linecurv); + + if (min_diff_linecurv == -1) + dewa->min_diff_linecurv = DefaultMinDiffLineCurv; + else + dewa->min_diff_linecurv = L_ABS(min_diff_linecurv); + + if (max_diff_linecurv == -1) + dewa->max_diff_linecurv = DefaultMaxDiffLineCurv; + else + dewa->max_diff_linecurv = L_ABS(max_diff_linecurv); + + if (max_edgecurv == -1) + dewa->max_edgecurv = DefaultMaxEdgeCurv; + else + dewa->max_edgecurv = L_ABS(max_edgecurv); + + if (max_diff_edgecurv == -1) + dewa->max_diff_edgecurv = DefaultMaxDiffEdgeCurv; + else + dewa->max_diff_edgecurv = L_ABS(max_diff_edgecurv); + + if (max_edgeslope == -1) + dewa->max_edgeslope = DefaultMaxEdgeSlope; + else + dewa->max_edgeslope = L_ABS(max_edgeslope); + + dewa->modelsready = 0; /* force validation */ + return 0; +} + + +/*! + * \brief dewarpaUseBothArrays() + * + * \param[in] dewa + * \param[in] useboth 0 for false, 1 for true + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This sets the useboth field. If set, this will attempt + * to apply both vertical and horizontal disparity arrays. + * Note that a model with only a vertical disparity array will + * always be valid. + * </pre> + */ +l_ok +dewarpaUseBothArrays(L_DEWARPA *dewa, + l_int32 useboth) +{ + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + dewa->useboth = useboth; + dewa->modelsready = 0; /* force validation */ + return 0; +} + + +/*! + * \brief dewarpaSetCheckColumns() + * + * \param[in] dewa + * \param[in] check_columns 0 for false, 1 for true + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This sets the 'check_columns" field. If set, and if + * 'useboth' is set, this will count the number of text + * columns. If the number is larger than 1, this will + * prevent the application of horizontal disparity arrays + * if they exist. + * (2) The check_columns field is set to TRUE by default. + * For horizontal disparity correction to take place on a + * single column of text, you must have: + * - a valid horizontal disparity array + * - useboth = 1 (TRUE) + * If there are multiple columns, in addition you need + * - check_columns = 0 (FALSE) + * + * </pre> + */ +l_ok +dewarpaSetCheckColumns(L_DEWARPA *dewa, + l_int32 check_columns) +{ + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + dewa->check_columns = check_columns; + return 0; +} + + +/*! + * \brief dewarpaSetMaxDistance() + * + * \param[in] dewa + * \param[in] maxdist for using ref models + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This sets the maxdist field. + * </pre> + */ +l_ok +dewarpaSetMaxDistance(L_DEWARPA *dewa, + l_int32 maxdist) +{ + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + dewa->maxdist = maxdist; + dewa->modelsready = 0; /* force validation */ + return 0; +} + + +/*----------------------------------------------------------------------* + * Dewarp serialized I/O * + *----------------------------------------------------------------------*/ +/*! + * \brief dewarpRead() + * + * \param[in] filename + * \return dew, or NULL on error + */ +L_DEWARP * +dewarpRead(const char *filename) +{ +FILE *fp; +L_DEWARP *dew; + + if (!filename) + return (L_DEWARP *)ERROR_PTR("filename not defined", __func__, NULL); + if ((fp = fopenReadStream(filename)) == NULL) + return (L_DEWARP *)ERROR_PTR_1("stream not opened", + filename, __func__, NULL); + + if ((dew = dewarpReadStream(fp)) == NULL) { + fclose(fp); + return (L_DEWARP *)ERROR_PTR_1("dew not read", + filename, __func__, NULL); + } + + fclose(fp); + return dew; +} + + +/*! + * \brief dewarpReadStream() + * + * \param[in] fp file stream + * \return dew dewarp, or NULL on error + * + * <pre> + * Notes: + * (1) The dewarp struct is stored in minimized format, with only + * subsampled disparity arrays. + * (2) The sampling and extra horizontal disparity parameters are + * stored here. During generation of the dewarp struct, they + * are passed in from the dewarpa. In readback, it is assumed + * that they are (a) the same for each page and (b) the same + * as the values used to create the dewarpa. + * </pre> + */ +L_DEWARP * +dewarpReadStream(FILE *fp) +{ +l_int32 version, sampling, redfactor, minlines, pageno, hasref, refpage; +l_int32 w, h, nx, ny, vdispar, hdispar, nlines; +l_int32 mincurv, maxcurv, leftslope, rightslope, leftcurv, rightcurv; +L_DEWARP *dew; +FPIX *fpixv = NULL, *fpixh = NULL; + + if (!fp) + return (L_DEWARP *)ERROR_PTR("stream not defined", __func__, NULL); + + if (fscanf(fp, "\nDewarp Version %d\n", &version) != 1) + return (L_DEWARP *)ERROR_PTR("not a dewarp file", __func__, NULL); + if (version != DEWARP_VERSION_NUMBER) + return (L_DEWARP *)ERROR_PTR("invalid dewarp version", __func__, NULL); + if (fscanf(fp, "pageno = %d\n", &pageno) != 1) + return (L_DEWARP *)ERROR_PTR("read fail for pageno", __func__, NULL); + if (fscanf(fp, "hasref = %d, refpage = %d\n", &hasref, &refpage) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for hasref, refpage", + __func__, NULL); + if (fscanf(fp, "sampling = %d, redfactor = %d\n", &sampling, &redfactor) + != 2) + return (L_DEWARP *)ERROR_PTR("read fail for sampling/redfactor", + __func__, NULL); + if (fscanf(fp, "nlines = %d, minlines = %d\n", &nlines, &minlines) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for nlines/minlines", + __func__, NULL); + if (fscanf(fp, "w = %d, h = %d\n", &w, &h) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for w, h", __func__, NULL); + if (fscanf(fp, "nx = %d, ny = %d\n", &nx, &ny) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for nx, ny", __func__, NULL); + if (fscanf(fp, "vert_dispar = %d, horiz_dispar = %d\n", &vdispar, &hdispar) + != 2) + return (L_DEWARP *)ERROR_PTR("read fail for flags", __func__, NULL); + if (vdispar) { + if (fscanf(fp, "min line curvature = %d, max line curvature = %d\n", + &mincurv, &maxcurv) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for mincurv & maxcurv", + __func__, NULL); + } + if (hdispar) { + if (fscanf(fp, "left edge slope = %d, right edge slope = %d\n", + &leftslope, &rightslope) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for leftslope & rightslope", + __func__, NULL); + if (fscanf(fp, "left edge curvature = %d, right edge curvature = %d\n", + &leftcurv, &rightcurv) != 2) + return (L_DEWARP *)ERROR_PTR("read fail for leftcurv & rightcurv", + __func__, NULL); + } + if (vdispar) { + if ((fpixv = fpixReadStream(fp)) == NULL) + return (L_DEWARP *)ERROR_PTR("read fail for vdispar", + __func__, NULL); + } + if (hdispar) { + if ((fpixh = fpixReadStream(fp)) == NULL) + return (L_DEWARP *)ERROR_PTR("read fail for hdispar", + __func__, NULL); + } + getc(fp); + + dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP)); + dew->w = w; + dew->h = h; + dew->pageno = pageno; + dew->sampling = sampling; + dew->redfactor = redfactor; + dew->minlines = minlines; + dew->nlines = nlines; + dew->hasref = hasref; + dew->refpage = refpage; + if (hasref == 0) /* any dew without a ref has an actual model */ + dew->vsuccess = 1; + dew->nx = nx; + dew->ny = ny; + if (vdispar) { + dew->mincurv = mincurv; + dew->maxcurv = maxcurv; + dew->vsuccess = 1; + dew->sampvdispar = fpixv; + } + if (hdispar) { + dew->leftslope = leftslope; + dew->rightslope = rightslope; + dew->leftcurv = leftcurv; + dew->rightcurv = rightcurv; + dew->hsuccess = 1; + dew->samphdispar = fpixh; + } + + return dew; +} + + +/*! + * \brief dewarpReadMem() + * + * \param[in] data serialization of dewarp + * \param[in] size of data in bytes + * \return dew dewarp, or NULL on error + */ +L_DEWARP * +dewarpReadMem(const l_uint8 *data, + size_t size) +{ +FILE *fp; +L_DEWARP *dew; + + if (!data) + return (L_DEWARP *)ERROR_PTR("data not defined", __func__, NULL); + if ((fp = fopenReadFromMemory(data, size)) == NULL) + return (L_DEWARP *)ERROR_PTR("stream not opened", __func__, NULL); + + dew = dewarpReadStream(fp); + fclose(fp); + if (!dew) L_ERROR("dew not read\n", __func__); + return dew; +} + + +/*! + * \brief dewarpWrite() + * + * \param[in] filename + * \param[in] dew + * \return 0 if OK, 1 on error + */ +l_ok +dewarpWrite(const char *filename, + L_DEWARP *dew) +{ +l_int32 ret; +FILE *fp; + + if (!filename) + return ERROR_INT("filename not defined", __func__, 1); + if (!dew) + return ERROR_INT("dew not defined", __func__, 1); + + if ((fp = fopenWriteStream(filename, "wb")) == NULL) + return ERROR_INT_1("stream not opened", filename, __func__, 1); + ret = dewarpWriteStream(fp, dew); + fclose(fp); + if (ret) + return ERROR_INT_1("dew not written to stream", filename, __func__, 1); + return 0; +} + + +/*! + * \brief dewarpWriteStream() + * + * \param[in] fp file stream opened for "wb" + * \param[in] dew + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This should not be written if there is no sampled + * vertical disparity array, which means that no model has + * been built for this page. + * </pre> + */ +l_ok +dewarpWriteStream(FILE *fp, + L_DEWARP *dew) +{ +l_int32 vdispar, hdispar; + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (!dew) + return ERROR_INT("dew not defined", __func__, 1); + + fprintf(fp, "\nDewarp Version %d\n", DEWARP_VERSION_NUMBER); + fprintf(fp, "pageno = %d\n", dew->pageno); + fprintf(fp, "hasref = %d, refpage = %d\n", dew->hasref, dew->refpage); + fprintf(fp, "sampling = %d, redfactor = %d\n", + dew->sampling, dew->redfactor); + fprintf(fp, "nlines = %d, minlines = %d\n", dew->nlines, dew->minlines); + fprintf(fp, "w = %d, h = %d\n", dew->w, dew->h); + fprintf(fp, "nx = %d, ny = %d\n", dew->nx, dew->ny); + vdispar = (dew->sampvdispar) ? 1 : 0; + hdispar = (dew->samphdispar) ? 1 : 0; + fprintf(fp, "vert_dispar = %d, horiz_dispar = %d\n", vdispar, hdispar); + if (vdispar) + fprintf(fp, "min line curvature = %d, max line curvature = %d\n", + dew->mincurv, dew->maxcurv); + if (hdispar) { + fprintf(fp, "left edge slope = %d, right edge slope = %d\n", + dew->leftslope, dew->rightslope); + fprintf(fp, "left edge curvature = %d, right edge curvature = %d\n", + dew->leftcurv, dew->rightcurv); + } + if (vdispar) fpixWriteStream(fp, dew->sampvdispar); + if (hdispar) fpixWriteStream(fp, dew->samphdispar); + fprintf(fp, "\n"); + + if (!vdispar) + L_WARNING("no disparity arrays!\n", __func__); + return 0; +} + + +/*! + * \brief dewarpWriteMem() + * + * \param[out] pdata data of serialized dewarp (not ascii) + * \param[out] psize size of returned data + * \param[in] dew + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Serializes a dewarp in memory and puts the result in a buffer. + * </pre> + */ +l_ok +dewarpWriteMem(l_uint8 **pdata, + size_t *psize, + L_DEWARP *dew) +{ +l_int32 ret; +FILE *fp; + + if (pdata) *pdata = NULL; + if (psize) *psize = 0; + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1); + if (!psize) + return ERROR_INT("&size not defined", __func__, 1); + if (!dew) + return ERROR_INT("dew not defined", __func__, 1); + +#if HAVE_FMEMOPEN + if ((fp = open_memstream((char **)pdata, psize)) == NULL) + return ERROR_INT("stream not opened", __func__, 1); + ret = dewarpWriteStream(fp, dew); + fputc('\0', fp); + fclose(fp); + if (*psize > 0) *psize = *psize - 1; +#else + L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__); + #ifdef _WIN32 + if ((fp = fopenWriteWinTempfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #else + if ((fp = tmpfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #endif /* _WIN32 */ + ret = dewarpWriteStream(fp, dew); + rewind(fp); + *pdata = l_binaryReadStream(fp, psize); + fclose(fp); +#endif /* HAVE_FMEMOPEN */ + return ret; +} + + +/*----------------------------------------------------------------------* + * Dewarpa serialized I/O * + *----------------------------------------------------------------------*/ +/*! + * \brief dewarpaRead() + * + * \param[in] filename + * \return dewa, or NULL on error + */ +L_DEWARPA * +dewarpaRead(const char *filename) +{ +FILE *fp; +L_DEWARPA *dewa; + + if (!filename) + return (L_DEWARPA *)ERROR_PTR("filename not defined", __func__, NULL); + if ((fp = fopenReadStream(filename)) == NULL) + return (L_DEWARPA *)ERROR_PTR_1("stream not opened", + filename, __func__, NULL); + + if ((dewa = dewarpaReadStream(fp)) == NULL) { + fclose(fp); + return (L_DEWARPA *)ERROR_PTR_1("dewa not read", + filename, __func__, NULL); + } + + fclose(fp); + return dewa; +} + + +/*! + * \brief dewarpaReadStream() + * + * \param[in] fp file stream + * \return dewa, or NULL on error + * + * <pre> + * Notes: + * (1) The serialized dewarp contains a Numa that gives the + * (increasing) page number of the dewarp structs that are + * contained. + * (2) Reference pages are added in after readback. + * </pre> + */ +L_DEWARPA * +dewarpaReadStream(FILE *fp) +{ +l_int32 i, version, ndewarp, maxpage; +l_int32 sampling, redfactor, minlines, maxdist, useboth; +l_int32 max_linecurv, min_diff_linecurv, max_diff_linecurv; +l_int32 max_edgeslope, max_edgecurv, max_diff_edgecurv; +L_DEWARP *dew; +L_DEWARPA *dewa; +NUMA *namodels; + + if (!fp) + return (L_DEWARPA *)ERROR_PTR("stream not defined", __func__, NULL); + + if (fscanf(fp, "\nDewarpa Version %d\n", &version) != 1) + return (L_DEWARPA *)ERROR_PTR("not a dewarpa file", __func__, NULL); + if (version != DEWARP_VERSION_NUMBER) + return (L_DEWARPA *)ERROR_PTR("invalid dewarp version", __func__, NULL); + + if (fscanf(fp, "ndewarp = %d, maxpage = %d\n", &ndewarp, &maxpage) != 2) + return (L_DEWARPA *)ERROR_PTR("read fail for maxpage+", __func__, NULL); + if (ndewarp < 1) + return (L_DEWARPA *)ERROR_PTR("pages not >= 1", __func__, NULL); + if (ndewarp > MaxPtrArraySize) + return (L_DEWARPA *)ERROR_PTR("too many pages", __func__, NULL); + if (fscanf(fp, + "sampling = %d, redfactor = %d, minlines = %d, maxdist = %d\n", + &sampling, &redfactor, &minlines, &maxdist) != 4) + return (L_DEWARPA *)ERROR_PTR("read fail for 4 params", __func__, NULL); + if (fscanf(fp, + "max_linecurv = %d, min_diff_linecurv = %d, max_diff_linecurv = %d\n", + &max_linecurv, &min_diff_linecurv, &max_diff_linecurv) != 3) + return (L_DEWARPA *)ERROR_PTR("read fail for linecurv", __func__, NULL); + if (fscanf(fp, + "max_edgeslope = %d, max_edgecurv = %d, max_diff_edgecurv = %d\n", + &max_edgeslope, &max_edgecurv, &max_diff_edgecurv) != 3) + return (L_DEWARPA *)ERROR_PTR("read fail for edgecurv", __func__, NULL); + if (fscanf(fp, "fullmodel = %d\n", &useboth) != 1) + return (L_DEWARPA *)ERROR_PTR("read fail for useboth", __func__, NULL); + + dewa = dewarpaCreate(maxpage + 1, sampling, redfactor, minlines, maxdist); + dewa->maxpage = maxpage; + dewa->max_linecurv = max_linecurv; + dewa->min_diff_linecurv = min_diff_linecurv; + dewa->max_diff_linecurv = max_diff_linecurv; + dewa->max_edgeslope = max_edgeslope; + dewa->max_edgecurv = max_edgecurv; + dewa->max_diff_edgecurv = max_diff_edgecurv; + dewa->useboth = useboth; + namodels = numaCreate(ndewarp); + dewa->namodels = namodels; + for (i = 0; i < ndewarp; i++) { + if ((dew = dewarpReadStream(fp)) == NULL) { + L_ERROR("read fail for dew[%d]\n", __func__, i); + dewarpaDestroy(&dewa); + return NULL; + } + dewarpaInsertDewarp(dewa, dew); + numaAddNumber(namodels, dew->pageno); + } + + /* Validate the models and insert reference models */ + dewarpaInsertRefModels(dewa, 0, 0); + return dewa; +} + + +/*! + * \brief dewarpaReadMem() + * + * \param[in] data serialization of dewarpa + * \param[in] size of data in bytes + * \return dewa dewarpa, or NULL on error + */ +L_DEWARPA * +dewarpaReadMem(const l_uint8 *data, + size_t size) +{ +FILE *fp; +L_DEWARPA *dewa; + + if (!data) + return (L_DEWARPA *)ERROR_PTR("data not defined", __func__, NULL); + if ((fp = fopenReadFromMemory(data, size)) == NULL) + return (L_DEWARPA *)ERROR_PTR("stream not opened", __func__, NULL); + + dewa = dewarpaReadStream(fp); + fclose(fp); + if (!dewa) L_ERROR("dewa not read\n", __func__); + return dewa; +} + + +/*! + * \brief dewarpaWrite() + * + * \param[in] filename + * \param[in] dewa + * \return 0 if OK, 1 on error + */ +l_ok +dewarpaWrite(const char *filename, + L_DEWARPA *dewa) +{ +l_int32 ret; +FILE *fp; + + if (!filename) + return ERROR_INT("filename not defined", __func__, 1); + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + if ((fp = fopenWriteStream(filename, "wb")) == NULL) + return ERROR_INT_1("stream not opened", filename, __func__, 1); + ret = dewarpaWriteStream(fp, dewa); + fclose(fp); + if (ret) + return ERROR_INT_1("dewa not written to stream", filename, __func__, 1); + return 0; +} + + +/*! + * \brief dewarpaWriteStream() + * + * \param[in] fp file stream opened for "wb" + * \param[in] dewa + * \return 0 if OK, 1 on error + */ +l_ok +dewarpaWriteStream(FILE *fp, + L_DEWARPA *dewa) +{ +l_int32 ndewarp, i, pageno; + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + + /* Generate the list of page numbers for which a model exists. + * Note that no attempt is made to determine if the model is + * valid, because that determination is associated with + * using the model to remove the warping, which typically + * can happen later, after all the models have been built. */ + dewarpaListPages(dewa); + if (!dewa->namodels) + return ERROR_INT("dewa->namodels not made", __func__, 1); + ndewarp = numaGetCount(dewa->namodels); /* with actual page models */ + + fprintf(fp, "\nDewarpa Version %d\n", DEWARP_VERSION_NUMBER); + fprintf(fp, "ndewarp = %d, maxpage = %d\n", ndewarp, dewa->maxpage); + fprintf(fp, "sampling = %d, redfactor = %d, minlines = %d, maxdist = %d\n", + dewa->sampling, dewa->redfactor, dewa->minlines, dewa->maxdist); + fprintf(fp, + "max_linecurv = %d, min_diff_linecurv = %d, max_diff_linecurv = %d\n", + dewa->max_linecurv, dewa->min_diff_linecurv, dewa->max_diff_linecurv); + fprintf(fp, + "max_edgeslope = %d, max_edgecurv = %d, max_diff_edgecurv = %d\n", + dewa->max_edgeslope, dewa->max_edgecurv, dewa->max_diff_edgecurv); + fprintf(fp, "fullmodel = %d\n", dewa->useboth); + for (i = 0; i < ndewarp; i++) { + numaGetIValue(dewa->namodels, i, &pageno); + dewarpWriteStream(fp, dewarpaGetDewarp(dewa, pageno)); + } + + return 0; +} + + +/*! + * \brief dewarpaWriteMem() + * + * \param[out] pdata data of serialized dewarpa (not ascii) + * \param[out] psize size of returned data + * \param[in] dewa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Serializes a dewarpa in memory and puts the result in a buffer. + * </pre> + */ +l_ok +dewarpaWriteMem(l_uint8 **pdata, + size_t *psize, + L_DEWARPA *dewa) +{ +l_int32 ret; +FILE *fp; + + if (pdata) *pdata = NULL; + if (psize) *psize = 0; + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1); + if (!psize) + return ERROR_INT("&size not defined", __func__, 1); + if (!dewa) + return ERROR_INT("dewa not defined", __func__, 1); + +#if HAVE_FMEMOPEN + if ((fp = open_memstream((char **)pdata, psize)) == NULL) + return ERROR_INT("stream not opened", __func__, 1); + ret = dewarpaWriteStream(fp, dewa); + fputc('\0', fp); + fclose(fp); + if (*psize > 0) *psize = *psize - 1; +#else + L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__); + #ifdef _WIN32 + if ((fp = fopenWriteWinTempfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #else + if ((fp = tmpfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #endif /* _WIN32 */ + ret = dewarpaWriteStream(fp, dewa); + rewind(fp); + *pdata = l_binaryReadStream(fp, psize); + fclose(fp); +#endif /* HAVE_FMEMOPEN */ + return ret; +}
